storysplat-viewer 2.7.7 → 2.7.8
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.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/storysplat-viewer.bundled.umd.js +1 -1
- package/dist/storysplat-viewer.bundled.umd.js.map +1 -1
- package/dist/storysplat-viewer.umd.js +1 -1
- package/dist/storysplat-viewer.umd.js.map +1 -1
- package/dist/types/dynamic-viewer/CameraControls.d.ts +5 -0
- package/dist/types/dynamic-viewer/viewerUI.d.ts +2 -0
- package/package.json +1 -1
package/dist/index.esm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.esm.js","sources":["../src/html-generation/generateHTML.ts","../src/transformers/sceneToConfig.ts","../src/dynamic-viewer/viewerUI.ts","../src/dynamic-viewer/CameraControls.ts","../src/dynamic-viewer/CharacterController.ts","../src/effects/gsplat-reveal-radial.ts","../src/effects/reveal-presets.ts","../node_modules/js-binary-schema-parser/lib/index.js","../node_modules/js-binary-schema-parser/lib/parsers/uint8.js","../node_modules/gifuct-js/lib/index.js","../node_modules/js-binary-schema-parser/lib/schemas/gif.js","../node_modules/gifuct-js/lib/deinterlace.js","../node_modules/gifuct-js/lib/lzw.js","../src/dynamic-viewer/AnimatedGifHelper.ts","../src/dynamic-viewer/HtmlMeshHelper.ts","../src/dynamic-viewer/CustomScriptSystem.ts","../src/dynamic-viewer/FrameSequencePlayer.ts","../src/dynamic-viewer/createViewer.ts","../src/dynamic-viewer/createViewerFromSceneId.ts","../src/editor/GizmoManager.ts","../src/editor/SelectionManager.ts","../src/editor/EditorCameraController.ts"],"sourcesContent":["/**\n * Simple HTML Generator for StorySplat\n *\n * Generates standalone HTML that loads the viewer from CDN.\n * This replaces the old 13K+ line HTML generation system with a simple\n * bootstrap HTML that uses the same viewer code as dynamic embedding.\n */\n\nimport type { SceneData } from '../types';\n\nexport interface GenerateHTMLOptions {\n /** Custom CDN URL for the viewer bundle. Default: unpkg */\n cdnUrl?: string;\n /** HTML page title. Default: scene name */\n title?: string;\n /** Meta description for SEO */\n description?: string;\n /** Favicon URL */\n faviconUrl?: string;\n /** Custom CSS to inject */\n customCSS?: string;\n /** Whether to minify the output */\n minify?: boolean;\n /** Enable lazy loading - shows thumbnail with start button before loading (overrides scene data) */\n lazyLoad?: boolean;\n /** Custom button text for lazy loading (overrides scene data) */\n lazyLoadButtonText?: string;\n}\n\n/**\n * Escape HTML entities in a string\n */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Escape string for safe embedding in inline <script> tags.\n * Prevents XSS by escaping </script> sequences that would close the script tag prematurely.\n */\nfunction escapeForInlineScript(str: string): string {\n // Escape </script> (case-insensitive) to prevent script tag injection\n // Using Unicode escapes for the < character within script context\n return str.replace(/<\\/script/gi, '<\\\\/script');\n}\n\n/**\n * Generate standalone HTML for a StorySplat scene\n *\n * @param sceneData - Scene data object (same format as createViewer)\n * @param options - Generation options\n * @returns Complete HTML string ready to be saved as .html file\n *\n * @example\n * ```typescript\n * import { generateHTML } from 'storysplat-viewer';\n *\n * const html = generateHTML(sceneData, { title: 'My Scene' });\n * // Save html to file or serve it\n * ```\n */\nexport function generateHTML(sceneData: SceneData, options: GenerateHTMLOptions = {}): string {\n const {\n cdnUrl = 'https://unpkg.com/storysplat-viewer@2/dist/storysplat-viewer.umd.js',\n title = sceneData.name || 'StorySplat Scene',\n description = `Interactive 3D scene: ${sceneData.name || 'StorySplat'}`,\n faviconUrl,\n customCSS = '',\n lazyLoad: optionsLazyLoad,\n lazyLoadButtonText: optionsLazyLoadButtonText\n } = options;\n\n // Resolve lazy load settings (options override scene data)\n const lazyLoad = optionsLazyLoad ?? sceneData.uiOptions?.lazyLoad ?? false;\n const lazyLoadButtonText = optionsLazyLoadButtonText ?? sceneData.uiOptions?.lazyLoadButtonText;\n\n // PlayCanvas CDN URL - loaded before viewer bundle (UMD expects 'pc' global)\n const playcanvasCdnUrl = 'https://cdn.jsdelivr.net/npm/playcanvas@2.14.3/build/playcanvas.min.js';\n\n const faviconTag = faviconUrl\n ? `<link rel=\"icon\" href=\"${escapeHtml(faviconUrl)}\" />`\n : '';\n\n const customCSSBlock = customCSS\n ? `<style>${customCSS}</style>`\n : '';\n\n // Serialize scene data, handling circular references\n // Then escape for safe embedding in inline <script> tags\n const rawJSON = JSON.stringify(sceneData, (key, value) => {\n // Skip any DOM nodes or circular refs\n // Check for HTMLElement safely (it doesn't exist in Node.js)\n if (typeof HTMLElement !== 'undefined' && value instanceof HTMLElement) return undefined;\n if (typeof value === 'function') return undefined;\n // Skip any object with nodeType (DOM nodes)\n if (value && typeof value === 'object' && 'nodeType' in value) return undefined;\n return value;\n }, 2);\n // Escape </script> sequences to prevent XSS attacks\n const sceneDataJSON = escapeForInlineScript(rawJSON);\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n <meta name=\"description\" content=\"${escapeHtml(description)}\">\n <title>${escapeHtml(title)}</title>\n ${faviconTag}\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body {\n width: 100%;\n /* Use 100dvh for iOS address bar support, with 100vh fallback */\n height: 100vh;\n height: 100dvh;\n overflow: hidden;\n background: #111;\n }\n #app {\n width: 100%;\n /* Use 100dvh for iOS address bar support, with 100vh fallback */\n height: 100vh;\n height: 100dvh;\n position: relative;\n }\n /* Loading state */\n #app:empty::after {\n content: 'Loading...';\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: #666;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 16px;\n }\n </style>\n ${customCSSBlock}\n</head>\n<body>\n <div id=\"app\"></div>\n\n <script src=\"${playcanvasCdnUrl}\"></script>\n <script src=\"${escapeHtml(cdnUrl)}\"></script>\n <script>\n (function() {\n 'use strict';\n\n var sceneData = ${sceneDataJSON};\n\n // Wait for DOM and viewer to be ready\n function init() {\n var container = document.getElementById('app');\n if (!container) {\n console.error('[StorySplat] Container #app not found');\n return;\n }\n\n if (typeof StorySplatViewer === 'undefined' || !StorySplatViewer.createViewer) {\n console.error('[StorySplat] Viewer not loaded. Check CDN URL.');\n return;\n }\n\n try {\n var viewer = StorySplatViewer.createViewer(container, sceneData, {\n showUI: true,\n autoPlay: false,\n lazyLoad: ${lazyLoad},\n lazyLoadButtonText: ${lazyLoadButtonText ? `'${escapeForInlineScript(lazyLoadButtonText)}'` : 'undefined'}\n });\n\n viewer.on('ready', function() {\n console.log('[StorySplat] Scene ready');\n });\n\n viewer.on('error', function(err) {\n console.error('[StorySplat] Error:', err);\n });\n\n // Expose viewer globally for debugging\n window.storySplatViewer = viewer;\n } catch (err) {\n console.error('[StorySplat] Failed to create viewer:', err);\n }\n }\n\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', init);\n } else {\n init();\n }\n })();\n </script>\n</body>\n</html>`;\n}\n\n/**\n * Generate HTML from a scene URL (fetches the JSON first)\n *\n * @param jsonUrl - URL to fetch scene JSON from\n * @param options - Generation options\n * @returns Complete HTML string\n */\nexport async function generateHTMLFromUrl(\n jsonUrl: string,\n options: GenerateHTMLOptions = {}\n): Promise<string> {\n const response = await fetch(jsonUrl);\n if (!response.ok) {\n throw new Error(`Failed to fetch scene: ${response.statusText}`);\n }\n const sceneData: SceneData = await response.json();\n return generateHTML(sceneData, options);\n}\n","/**\n * Scene Data to ExportProps Transformer\n *\n * Converts Firebase scene JSON or direct scene data to the ExportProps format\n * used by the HTML generation system.\n */\nimport type { SceneData, WaypointData, ExportProps, SplatSwapPoint, HotspotData, PortalData, CustomMeshConfig, HtmlMeshConfig, LightConfig, ParticleConfig, CollisionMeshConfig, AudioEmitterConfig, TemplateType } from '../types';\ntype InteractionDataWithText = {\n text?: unknown;\n};\nfunction getInteractionText(data: unknown): string | undefined {\n if (!data || typeof data !== 'object')\n return undefined;\n const maybe = data as InteractionDataWithText;\n return typeof maybe.text === 'string' ? maybe.text : undefined;\n}\n/**\n * Transform scene data to ExportProps format\n */\nexport function transformSceneToExportProps(scene: SceneData): ExportProps {\n // Get all available URLs\n const originalUrl = scene.loadedModelUrl || scene.splatUrl || '';\n const sogUrl = scene.sogModelUrl || scene.sogUrl;\n const compressedPlyUrl = scene.compressedPlyUrl;\n const lodMetaUrl = scene.lodMetaUrl; // LOD streaming format (highest priority)\n // Skybox: prefer the new nested format (`scene.skybox`), but support legacy fields used by the editor.\n // This ensures older stored scene JSON still renders a skybox in the viewer.\n const legacySkyboxUrl = (typeof scene.activeSkyboxUrl === 'string' && scene.activeSkyboxUrl) ? scene.activeSkyboxUrl :\n (typeof scene.skyboxUrl === 'string' && scene.skyboxUrl) ? scene.skyboxUrl :\n undefined;\n const resolvedSkybox = scene.skybox || (legacySkyboxUrl ? {\n url: legacySkyboxUrl,\n rotation: scene.skyboxRotation\n } : undefined);\n // For PlayCanvas, prefer formats in this order: LOD > SOG > Compressed PLY > PLY > Original\n // PlayCanvas can't directly load .splat files, so we need PLY-compatible formats\n const originalExt = originalUrl.split('?')[0].split('.').pop()?.toLowerCase();\n const isPlayCanvasCompatible = originalExt === 'ply' || originalUrl.includes('.compressed.ply');\n // Choose the best URL for PlayCanvas (for backward compatibility, splatUrl is the main URL)\n // LOD streaming is handled separately as lodMetaUrl\n let splatUrl = originalUrl;\n if (sogUrl) {\n splatUrl = sogUrl; // SOG is best (after LOD)\n }\n else if (compressedPlyUrl) {\n splatUrl = compressedPlyUrl; // Compressed PLY is second best\n }\n else if (!isPlayCanvasCompatible) {\n // Original is not compatible, keep it but warn\n console.warn('[StorySplat Viewer] Original file format may not be compatible with PlayCanvas:', originalExt);\n }\n console.log('[StorySplat Viewer] URL selection:', {\n original: originalUrl,\n lodMetaUrl,\n sogUrl,\n compressedPlyUrl,\n selected: splatUrl\n });\n // Transform waypoints\n const waypoints = (scene.waypoints || []).map((wp: WaypointData) => {\n // Handle FOV - convert from radians to degrees if necessary\n // (matching waypointNavigation.ts logic)\n let fov = wp.fov || 60;\n if (fov < 3.5) {\n // FOV is in radians, convert to degrees\n fov = fov * (180 / Math.PI);\n }\n // Extract info from interactions (type: 'info') if not directly on waypoint\n // In SceneTypes.ts, waypoint info is stored in interactions array with type 'info'\n let info = wp.info || wp.description || '';\n if (!info && wp.interactions) {\n const infoInteraction = wp.interactions.find((i) => i.type === 'info');\n const text = getInteractionText(infoInteraction?.data);\n if (text) {\n info = text;\n }\n }\n return {\n position: wp.position || { x: wp.x || 0, y: wp.y || 0, z: wp.z || 0 },\n rotation: wp.rotation || { x: 0, y: 0, z: 0 },\n fov,\n duration: wp.duration || 2000,\n name: wp.name || wp.title || '',\n info,\n interactions: wp.interactions || [],\n triggerDistance: wp.triggerDistance ?? 1.0\n };\n });\n // Backward-compatible particle fallback:\n // prefer particleSystems only when it actually contains entries, otherwise use legacy particles.\n const resolvedParticles = Array.isArray(scene.particleSystems) && scene.particleSystems.length > 0\n ? scene.particleSystems\n : (scene.particles || []);\n // Build ExportProps\n const exportProps: ExportProps = {\n // Scene Metadata\n name: scene.name || 'StorySplat Scene',\n sceneId: scene.sceneId,\n userId: scene.userId,\n userName: scene.userName || 'Unknown',\n thumbnailUrl: scene.thumbnailUrl,\n // Splat Configuration\n splatUrl,\n sogUrl,\n lodMetaUrl,\n // Build fallback URLs from all available formats (excluding the primary)\n fallbackUrls: [\n ...(scene.fallbackUrls || []),\n // Add other available formats as fallbacks\n sogUrl !== splatUrl ? sogUrl : null,\n compressedPlyUrl !== splatUrl ? compressedPlyUrl : null,\n originalUrl !== splatUrl ? originalUrl : null\n ].filter((url): url is string => url != null && url !== ''),\n // Handle both splatScale (number) and scale (object) formats\n scale: scene.scale || (scene.splatScale != null\n ? { x: scene.splatScale, y: scene.splatScale, z: scene.splatScale }\n : { x: 1, y: 1, z: 1 }),\n // Handle both array and object position formats\n splatPosition: scene.splatPosition || scene.position || scene.cameraPosition || [0, 0, 0],\n splatRotation: scene.splatRotation || scene.rotation || scene.cameraRotation || [0, 0, 0],\n invertXScale: scene.invertXScale,\n invertYScale: scene.invertYScale,\n // Scene Data\n waypoints,\n hotspots: scene.hotspots || [],\n portals: scene.portals || [],\n // Prefer nested skybox, but keep flat properties populated for compatibility.\n skybox: resolvedSkybox,\n skyboxUrl: resolvedSkybox?.url,\n skyboxRotation: resolvedSkybox?.rotation,\n customMeshes: scene.customMeshes || [],\n htmlMeshes: scene.htmlMeshes || [],\n lights: scene.lights || [],\n particles: resolvedParticles,\n collisionMeshesData: scene.collisionMeshesData || [],\n playerHeight: scene.playerHeight,\n // UI Configuration\n uiColor: scene.uiColor || '#ffffff',\n uiOptions: {\n showStartExperience: scene.uiOptions?.showStartExperience ?? true,\n showWatermark: scene.uiOptions?.showWatermark ?? true,\n hideFullscreenButton: scene.uiOptions?.hideFullscreenButton ?? false,\n hideInfoButton: scene.uiOptions?.hideInfoButton ?? false,\n hideMuteButton: scene.uiOptions?.hideMuteButton ?? false,\n hideHelpButton: scene.uiOptions?.hideHelpButton ?? false,\n hideNavigator: scene.uiOptions?.hideNavigator ?? false,\n showWaypointList: scene.uiOptions?.showWaypointList ?? true,\n hideWatermark: scene.uiOptions?.hideWatermark ?? false,\n watermarkText: scene.uiOptions?.watermarkText,\n watermarkLink: scene.uiOptions?.watermarkLink,\n buttonPosition: scene.uiOptions?.buttonPosition || 'inline',\n buttonLabels: scene.uiOptions?.buttonLabels,\n // Whitelabeling option for custom preloader logo\n customPreloaderLogoUrl: scene.uiOptions?.customPreloaderLogoUrl,\n // Lazy loading options\n lazyLoad: scene.uiOptions?.lazyLoad,\n lazyLoadButtonText: scene.uiOptions?.lazyLoadButtonText,\n lazyLoadThumbnailUrl: scene.uiOptions?.lazyLoadThumbnailUrl,\n lazyLoadThumbnailType: scene.uiOptions?.lazyLoadThumbnailType,\n // Layout type\n uiType: scene.uiOptions?.uiType,\n // Debug mode (FPS counter)\n debugMode: scene.uiOptions?.debugMode\n },\n // Camera Configuration\n defaultCameraMode: scene.defaultCameraMode || 'orbit',\n allowedCameraModes: scene.allowedCameraModes || ['orbit', 'first-person', 'drone'],\n cameraMovementSpeed: scene.cameraMovementSpeed,\n cameraRotationSensitivity: scene.cameraRotationSensitivity,\n cameraDamping: scene.cameraDamping,\n invertCameraRotation: scene.invertCameraRotation,\n // Navigation Configuration\n includeScrollControls: scene.includeScrollControls ?? true,\n scrollButtonMode: scene.scrollButtonMode || 'continuous',\n scrollAmount: scene.scrollAmount || 100,\n scrollSpeed: scene.scrollSpeed,\n transitionSpeed: scene.transitionSpeed,\n autoPlayEnabled: scene.autoPlayEnabled ?? false,\n // Playback settings\n autoplaySpeed: scene.autoplaySpeed,\n loopMode: scene.loopMode,\n // XR Configuration\n includeXR: scene.includeXR,\n xrMode: scene.xrMode,\n // Custom Script\n customScript: scene.customScript,\n // Template type (read from scene uiOptions, default to minimal)\n templateType: (scene.uiOptions?.uiType as TemplateType) || 'minimal',\n // Splat Swap System\n additionalSplats: scene.additionalSplats || [],\n keepMeshesInMemory: scene.keepMeshesInMemory ?? false,\n initialSplatExploreMode: scene.initialSplatExploreMode,\n // Audio emitters\n audioEmitters: scene.audioEmitters || [],\n // 4DGS frame sequence\n frameSequence: scene.frameSequence\n };\n return exportProps;\n}\n/**\n * Viewer configuration for dynamic viewer runtime\n */\nexport interface ViewerConfig {\n splatUrl: string;\n sogUrl?: string;\n /** LOD streaming format URL (lod-meta.json) - highest priority for loading */\n lodMetaUrl?: string;\n fallbackUrls?: string[];\n scale?: {\n x: number;\n y: number;\n z: number;\n };\n position: number[];\n rotation: number[];\n invertXScale?: boolean;\n invertYScale?: boolean;\n waypoints?: WaypointData[];\n hotspots?: HotspotData[];\n portals?: PortalData[];\n /** Nested skybox config (newer format) */\n skybox?: {\n url: string;\n rotation?: number;\n intensity?: number;\n enableIBL?: boolean;\n };\n /** Flat skybox URL (older format, for backwards compatibility) */\n skyboxUrl?: string;\n /** Flat skybox rotation in radians (older format, for backwards compatibility) */\n skyboxRotation?: number;\n customMeshes?: CustomMeshConfig[];\n htmlMeshes?: HtmlMeshConfig[];\n lights?: LightConfig[];\n particles?: ParticleConfig[];\n collisionMeshesData?: CollisionMeshConfig[];\n cameraMode?: string;\n defaultCameraMode?: string;\n allowedCameraModes?: string[];\n autoPlay?: boolean;\n uiColor?: string;\n uiOptions?: SceneData['uiOptions'];\n // Camera settings (matching HTML export CONFIG)\n fov?: number;\n nearClip?: number;\n farClip?: number;\n playerHeight?: number;\n cameraMovementSpeed?: number;\n cameraRotationSensitivity?: number;\n cameraDamping?: number;\n invertCameraRotation?: boolean;\n // Navigation settings\n scrollSpeed?: number;\n scrollAmount?: number;\n scrollButtonMode?: 'continuous' | 'incremental' | 'percentage' | 'waypoint';\n transitionSpeed?: number;\n // Playback settings\n /** Speed of autoplay in progress per second (default: calculated from waypoint durations) */\n autoplaySpeed?: number;\n /** Loop behavior for playback: 'loop' restarts at beginning, 'pingpong' reverses direction, 'none' stops at end */\n loopMode?: 'loop' | 'pingpong' | 'none';\n // Splat Swap System\n additionalSplats?: SplatSwapPoint[];\n keepMeshesInMemory?: boolean;\n /** Default explore sub-mode for the initial splat. 'orbit' for aerial views, 'fly' for interior POV. */\n initialSplatExploreMode?: 'orbit' | 'fly';\n // XR Configuration\n includeXR?: boolean;\n xrMode?: 'ar' | 'vr' | 'both';\n // Custom Script\n customScript?: string;\n // Background color\n backgroundColor?: string;\n // Audio emitters\n audioEmitters?: AudioEmitterConfig[];\n // 4DGS frame sequence\n frameSequence?: {\n frameUrls: string[];\n fps?: number;\n loop?: boolean;\n preloadCount?: number;\n autoplay?: boolean;\n };\n}\n/**\n * Transform ExportProps to scene config for dynamic viewer\n * This is a simpler format for runtime use\n */\nexport function exportPropsToViewerConfig(props: ExportProps): ViewerConfig {\n // Get FOV from first waypoint if available, otherwise default to 60\n const fov = props.waypoints?.[0]?.fov || 60;\n return {\n splatUrl: props.splatUrl,\n sogUrl: props.sogUrl,\n lodMetaUrl: props.lodMetaUrl,\n fallbackUrls: props.fallbackUrls,\n scale: props.scale,\n position: props.splatPosition || [0, 0, 0],\n rotation: props.splatRotation || [0, 0, 0],\n invertXScale: props.invertXScale,\n invertYScale: props.invertYScale,\n waypoints: props.waypoints,\n hotspots: props.hotspots,\n portals: props.portals,\n skybox: props.skybox,\n skyboxUrl: props.skyboxUrl,\n skyboxRotation: props.skyboxRotation,\n customMeshes: props.customMeshes,\n htmlMeshes: props.htmlMeshes,\n lights: props.lights,\n particles: props.particles,\n collisionMeshesData: props.collisionMeshesData,\n cameraMode: props.defaultCameraMode,\n defaultCameraMode: props.defaultCameraMode,\n allowedCameraModes: props.allowedCameraModes,\n autoPlay: props.autoPlayEnabled,\n uiColor: props.uiColor,\n uiOptions: props.uiOptions,\n // Camera settings (matching HTML export CONFIG)\n fov: fov,\n nearClip: props.minClipPlane || 0.1,\n farClip: props.maxClipPlane || 1000,\n playerHeight: props.playerHeight || 1.6,\n cameraMovementSpeed: props.cameraMovementSpeed || 1,\n cameraRotationSensitivity: props.cameraRotationSensitivity || 0.2,\n cameraDamping: props.cameraDamping || 0.75,\n invertCameraRotation: props.invertCameraRotation,\n // Navigation settings\n scrollSpeed: props.scrollSpeed,\n scrollAmount: props.scrollAmount,\n scrollButtonMode: props.scrollButtonMode,\n transitionSpeed: props.transitionSpeed,\n // Playback settings\n autoplaySpeed: props.autoplaySpeed,\n loopMode: props.loopMode,\n // Splat Swap System\n additionalSplats: props.additionalSplats,\n keepMeshesInMemory: props.keepMeshesInMemory ?? false,\n initialSplatExploreMode: props.initialSplatExploreMode,\n // XR Configuration\n includeXR: props.includeXR,\n xrMode: props.xrMode,\n // Custom Script\n customScript: props.customScript,\n // Audio emitters\n audioEmitters: props.audioEmitters || [],\n // 4DGS frame sequence\n frameSequence: props.frameSequence\n };\n}\n","/**\n * Dynamic Viewer UI Module\n *\n * Generates and injects UI elements and CSS for the dynamic viewer.\n * Uses MINIMAL theme for embedded viewers.\n */\nimport type { ViewerConfig, ButtonLabels, WaypointData, HotspotData } from '../types';\ninterface ViewerUIController {\n nextWaypoint: () => void;\n prevWaypoint: () => void;\n play: () => void;\n pause: () => void;\n isPlaying: () => boolean;\n getCurrentWaypointIndex: () => number;\n getWaypointCount: () => number;\n getWaypoints?: () => WaypointData[];\n setCameraMode?: (mode: string) => void;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n on: (event: string, callback: (...args: any[]) => void) => void;\n}\ninterface WebkitFullscreenDocument extends Document {\n webkitFullscreenElement?: Element | null;\n webkitExitFullscreen?: () => Promise<void> | void;\n}\ninterface WebkitFullscreenElement extends HTMLElement {\n webkitRequestFullscreen?: () => Promise<void> | void;\n}\n/**\n * Default button labels for internationalization (i18n)\n */\nexport const DEFAULT_BUTTON_LABELS: Required<ButtonLabels> = {\n tour: 'Tour',\n explore: 'Explore',\n hybrid: 'Hybrid',\n walk: 'Walk',\n orbit: 'Orbit',\n fly: 'Fly',\n next: 'Next',\n previous: 'Prev',\n startExperience: 'Start Experience',\n fullscreen: 'Fullscreen',\n mute: 'Mute',\n unmute: 'Unmute',\n waypoints: 'Waypoints',\n close: 'Close',\n yes: 'Yes',\n cancel: 'Cancel',\n switchScenes: 'Switch scenes?',\n hotspotDefaultTitle: 'Hotspot',\n openExternalLink: 'Open External Link',\n vr: 'VR',\n ar: 'AR',\n exitVr: 'Exit VR',\n exitAr: 'Exit AR',\n loading: 'Loading...',\n loadingScene: 'Loading {name}...',\n helpTitle: 'Controls & Help',\n helpCameraModes: 'Camera Modes:',\n helpTourDesc: 'Follow predefined path',\n helpExploreDesc: 'Free movement',\n helpWalkDesc: 'First-person walking',\n errorWebGLTitle: 'Unable to Initialize 3D Graphics',\n errorWebGLMessage: 'Your browser or device may not support WebGL. Please try a different browser or device.',\n percentageFormat: '{n}%'\n};\n/**\n * Get a button label, falling back to default if not provided\n */\nexport function getButtonLabel(labels: ButtonLabels | undefined, key: keyof ButtonLabels): string {\n return labels?.[key] || DEFAULT_BUTTON_LABELS[key];\n}\n/**\n * Format a percentage label using the configured format\n */\nfunction formatPercentageLabel(labels: ButtonLabels | undefined, value: number): string {\n const format = labels?.percentageFormat || DEFAULT_BUTTON_LABELS.percentageFormat;\n return format.replace('{n}', String(value));\n}\nexport interface UIOptions {\n uiColor?: string;\n showScrollControls?: boolean;\n showModeToggle?: boolean;\n showFullscreenButton?: boolean;\n showHelpButton?: boolean;\n showPreloader?: boolean;\n allowedCameraModes?: string[];\n defaultCameraMode?: string;\n customPreloaderLogoUrl?: string;\n /** Custom button labels for internationalization (i18n) */\n buttonLabels?: ButtonLabels;\n /** Whether to hide the watermark (whitelabeling) */\n hideWatermark?: boolean;\n /** Custom watermark text (whitelabeling) */\n watermarkText?: string;\n /** Custom watermark link URL (whitelabeling) */\n watermarkLink?: string;\n /** Scene ID for default watermark link */\n sceneId?: string;\n /** Show dropdown list for quick waypoint navigation */\n showWaypointList?: boolean;\n /** UI layout template: 'minimal' (default), 'standard' (centered), 'pro' (dark) */\n template?: 'standard' | 'minimal' | 'pro';\n /** Enable debug mode (shows FPS counter) */\n debugMode?: boolean;\n}\nexport interface UIElements {\n preloader?: HTMLElement;\n scrollControls?: HTMLElement;\n modeToggle?: HTMLElement;\n fullscreenButton?: HTMLElement;\n helpButton?: HTMLElement;\n helpPanel?: HTMLElement;\n waypointInfo?: HTMLElement;\n progressBar?: HTMLElement;\n progressText?: HTMLElement;\n hotspotPopup?: HTMLElement;\n portalPopup?: HTMLElement;\n joystick?: HTMLElement;\n joystickThumb?: HTMLElement;\n lookZone?: HTMLElement;\n vrButton?: HTMLElement;\n arButton?: HTMLElement;\n watermark?: HTMLElement;\n waypointListContainer?: HTMLElement;\n fpsCounter?: HTMLElement;\n}\n/**\n * Generate CSS styles for the viewer UI.\n * Base styles are always the minimal theme. Standard and pro templates\n * are applied as CSS override blocks appended after the base.\n */\nexport function generateViewerStyles(uiColor: string = '#4CAF50', template: string = 'minimal'): string {\n return `\n /* Container - CRITICAL: must be position relative and contained */\n .storysplat-viewer-container {\n position: relative;\n width: 100%;\n height: 100%;\n overflow: hidden;\n font-family: system-ui, -apple-system, sans-serif;\n box-sizing: border-box;\n }\n\n .storysplat-viewer-container * {\n box-sizing: border-box;\n }\n\n /* Style isolation: reset common elements to prevent parent page styles from leaking in.\n Uses :where() for zero specificity so individual class rules always win. */\n .storysplat-viewer-container :where(button, a) {\n all: unset;\n box-sizing: border-box;\n display: inline-block;\n font-family: inherit;\n font-size: inherit;\n line-height: normal;\n cursor: pointer;\n }\n\n .storysplat-viewer-container button:focus-visible,\n .storysplat-viewer-container a:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 2px;\n }\n\n .storysplat-viewer-container canvas {\n position: absolute;\n top: 0;\n left: 0;\n width: 100% !important;\n height: 100% !important;\n display: block;\n touch-action: none;\n }\n\n /* Preloader - semi-transparent to see loading/reveal effect */\n .storysplat-preloader {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(30, 30, 30, 0.85);\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n z-index: 100000;\n transition: opacity 0.5s ease-out;\n }\n\n .storysplat-preloader.hidden {\n opacity: 0;\n pointer-events: none;\n }\n\n .storysplat-preloader-content {\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 40px;\n gap: 20px;\n }\n\n .storysplat-preloader-media {\n display: flex;\n align-items: center;\n gap: 40px;\n }\n\n .storysplat-preloader-image {\n height: 200px;\n width: auto;\n object-fit: contain;\n }\n\n .storysplat-preloader-image-inverted {\n height: 200px;\n width: auto;\n object-fit: contain;\n filter: invert(1);\n margin-right: -100px;\n }\n\n .storysplat-preloader-lottie {\n height: 200px;\n width: 200px;\n }\n\n @media (max-width: 768px) {\n .storysplat-preloader-image,\n .storysplat-preloader-image-inverted {\n height: 100px;\n }\n .storysplat-preloader-image-inverted {\n margin-right: -50px;\n }\n .storysplat-preloader-lottie {\n height: 100px;\n width: 100px;\n }\n }\n\n .storysplat-preloader-progress {\n width: 200px;\n height: 4px;\n background: rgba(255, 255, 255, 0.1);\n border-radius: 2px;\n margin-top: 20px;\n position: relative;\n }\n\n .storysplat-preloader-bar {\n width: 0%;\n height: 100%;\n background: ${uiColor};\n border-radius: 2px;\n transition: width 0.3s ease-out;\n }\n\n .storysplat-preloader-text {\n position: absolute;\n top: -25px;\n left: 50%;\n transform: translateX(-50%);\n color: white;\n font-size: 14px;\n white-space: nowrap;\n }\n\n /* Minimal Scroll Controls */\n .storysplat-scroll-controls {\n position: absolute;\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n width: 150px;\n padding: 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 5px;\n z-index: 1000;\n }\n\n .storysplat-scroll-content {\n width: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 5px;\n }\n\n .storysplat-progress-text {\n font-size: 14px;\n color: white;\n font-variant-numeric: tabular-nums;\n min-width: 40px;\n text-align: center;\n }\n\n .storysplat-progress-container {\n width: 90%;\n max-width: 150px;\n height: 3px;\n background: rgba(255, 255, 255, 0.2);\n border-radius: 2px;\n overflow: hidden;\n }\n\n .storysplat-progress-bar {\n height: 100%;\n background: ${uiColor};\n transition: width 0.3s ease-out;\n width: 0%;\n }\n\n .storysplat-scroll-buttons {\n display: flex;\n justify-content: center;\n gap: 5px;\n z-index: 1000;\n }\n\n /* Minimal Button Style */\n .storysplat-btn {\n background: rgba(0, 0, 0, 0.3);\n border: none;\n color: white;\n padding: 4px 8px;\n font-size: 12px;\n border-radius: 3px;\n cursor: pointer;\n transition: background 0.2s;\n text-align: center;\n }\n\n .storysplat-btn:hover {\n background: rgba(0, 0, 0, 0.5);\n }\n\n .storysplat-btn-play {\n background: rgba(0, 0, 0, 0.3);\n border: none;\n color: white;\n padding: 4px 8px;\n border-radius: 3px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .storysplat-btn-play svg {\n width: 12px;\n height: 12px;\n fill: white;\n }\n\n /* Explore Controls (Orbit/Fly toggle) */\n .storysplat-explore-controls {\n display: none;\n gap: 5px;\n width: 100%;\n justify-content: center;\n }\n\n .storysplat-explore-controls.visible {\n display: flex;\n }\n\n .storysplat-explore-btn {\n flex: 1;\n background: rgba(0, 0, 0, 0.3);\n border: none;\n color: white;\n padding: 6px 12px;\n font-size: 12px;\n border-radius: 3px;\n cursor: pointer;\n transition: background 0.2s;\n text-align: center;\n }\n\n .storysplat-explore-btn.selected {\n background: ${uiColor} !important;\n }\n\n .storysplat-explore-btn:hover {\n background: rgba(0, 0, 0, 0.5);\n }\n\n /* Mode Toggle - Minimal */\n .storysplat-mode-container {\n margin-top: 5px;\n }\n\n .storysplat-mode-toggle {\n display: flex;\n gap: 5px;\n }\n\n .storysplat-mode-btn {\n background: rgba(0, 0, 0, 0.3);\n border: none;\n color: white;\n padding: 3px 6px;\n font-size: 11px;\n border-radius: 2px;\n cursor: pointer;\n transition: all 0.2s ease;\n text-align: center;\n }\n\n .storysplat-mode-btn.selected {\n background: ${uiColor} !important;\n }\n\n .storysplat-mode-btn:hover {\n background: rgba(0, 0, 0, 0.5);\n }\n\n /* Fullscreen Button - Minimal */\n .storysplat-fullscreen-btn {\n position: absolute;\n top: 10px;\n right: 10px;\n background: rgba(0, 0, 0, 0);\n border: none;\n padding: 5px;\n border-radius: 3px;\n cursor: pointer;\n z-index: 1000;\n }\n\n .storysplat-fullscreen-btn svg {\n width: 20px;\n height: 20px;\n fill: white;\n }\n\n /* Help Button - Minimal */\n .storysplat-help-btn {\n position: absolute;\n top: 10px;\n left: 10px;\n width: 30px;\n height: 30px;\n border-radius: 50%;\n background: rgba(0, 0, 0, 0.3);\n color: white;\n border: none;\n cursor: pointer;\n font-size: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n transition: all 0.2s ease;\n }\n\n .storysplat-help-btn:hover {\n background: rgba(0, 0, 0, 0.5);\n }\n\n .storysplat-help-panel {\n position: absolute;\n top: 50px;\n left: 10px;\n background: rgba(0, 0, 0, 0.8);\n color: white;\n padding: 15px;\n border-radius: 5px;\n max-width: 280px;\n z-index: 999;\n display: none;\n font-size: 12px;\n }\n\n .storysplat-help-panel.visible {\n display: block;\n }\n\n /* XR (VR/AR) Buttons - Minimal */\n .storysplat-xr-btn {\n position: absolute;\n top: 10px;\n right: 45px;\n background: rgba(0, 0, 0, 0.5);\n border: none;\n padding: 6px 10px;\n border-radius: 4px;\n cursor: pointer;\n z-index: 1000;\n color: white;\n font-size: 11px;\n font-weight: bold;\n text-transform: uppercase;\n transition: all 0.2s ease;\n display: none;\n }\n\n .storysplat-xr-btn.available {\n display: block;\n }\n\n .storysplat-xr-btn.active {\n background: ${uiColor};\n }\n\n .storysplat-xr-btn:hover {\n background: rgba(0, 0, 0, 0.7);\n }\n\n .storysplat-xr-btn.active:hover {\n background: ${uiColor};\n opacity: 0.9;\n }\n\n .storysplat-ar-btn {\n right: 85px;\n }\n\n .storysplat-help-panel h3 {\n margin: 0 0 10px 0;\n font-size: 14px;\n font-weight: 600;\n }\n\n .storysplat-help-panel p {\n margin: 5px 0;\n line-height: 1.4;\n }\n\n /* Waypoint Info - Top Banner Style (matching BabylonJS minimal export) */\n .storysplat-waypoint-info {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n background: rgba(0, 0, 0, 0.5);\n color: white;\n text-align: center;\n z-index: 1000;\n pointer-events: none;\n display: none;\n }\n\n .storysplat-waypoint-info.hasContent {\n display: block;\n padding: 50px 30px 30px 30px;\n }\n\n .storysplat-waypoint-info h2 {\n margin: 0 0 8px 0;\n font-size: 18px;\n font-weight: bold;\n }\n\n .storysplat-waypoint-info p {\n margin: 0;\n font-size: 14px;\n line-height: 1.5;\n opacity: 0.9;\n }\n\n /* Responsive waypoint info */\n @media (max-height: 600px) {\n .storysplat-waypoint-info.hasContent {\n padding: 20px 20px 15px 20px;\n font-size: 13px;\n }\n }\n\n @media (max-height: 500px) {\n .storysplat-waypoint-info.hasContent {\n padding: 10px 15px 10px 15px;\n font-size: 12px;\n }\n }\n\n /* Hotspot Popup - Matching BabylonJS HTML export styles */\n /* Uses position: absolute so popup stays within container when embedded */\n .storysplat-hotspot-popup {\n position: absolute;\n background-color: rgba(0, 0, 0, 0.75);\n color: white;\n padding: 20px;\n border-radius: 10px;\n z-index: 100001;\n box-shadow: 0 0 10px rgba(0,0,0,0.5);\n display: none;\n font-size: 14px;\n flex-direction: column;\n font-family: system-ui, -apple-system, sans-serif;\n /* Default centered positioning */\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n max-width: 80%;\n max-height: 80%;\n overflow: auto;\n }\n\n .storysplat-hotspot-popup.visible {\n display: flex;\n }\n\n /* Fullscreen mode for click activation with image/iframe content */\n .storysplat-hotspot-popup.fullscreen {\n top: 20px !important;\n left: 20px !important;\n right: 20px !important;\n bottom: 20px !important;\n transform: none !important;\n width: auto !important;\n height: auto !important;\n max-width: none !important;\n max-height: none !important;\n margin: 0 !important;\n border-radius: 10px !important;\n display: flex !important;\n align-items: center !important;\n justify-content: center !important;\n flex-direction: column !important;\n padding: 0 !important;\n overflow: hidden !important;\n }\n\n .storysplat-hotspot-popup h2,\n .storysplat-hotspot-popup-title {\n margin: 0 0 10px 0;\n font-size: 18px;\n font-weight: 600;\n padding-right: 30px;\n }\n\n .storysplat-hotspot-popup p {\n margin: 0 0 10px 0;\n line-height: 1.5;\n font-size: 14px;\n white-space: pre-wrap;\n max-width: 500px;\n }\n\n .storysplat-hotspot-popup-content {\n max-width: 500px;\n }\n\n .storysplat-hotspot-popup.fullscreen .storysplat-hotspot-popup-title {\n text-align: center;\n width: 100%;\n padding: 20px 20px 10px 20px;\n padding-right: 20px;\n flex-shrink: 0;\n }\n\n .storysplat-hotspot-popup.fullscreen .storysplat-hotspot-popup-content {\n display: flex;\n flex-direction: column;\n align-items: center;\n text-align: center;\n width: 100%;\n max-width: none;\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n }\n\n .storysplat-hotspot-popup.fullscreen p {\n text-align: center;\n max-width: 80%;\n }\n\n .storysplat-hotspot-popup.fullscreen .storysplat-hotspot-popup-link {\n margin: 4px auto;\n }\n\n .storysplat-hotspot-popup.fullscreen .storysplat-hotspot-popup-close {\n display: block;\n margin: 4px auto 20px auto;\n flex-shrink: 0;\n }\n\n .storysplat-hotspot-popup img {\n max-width: 100%;\n height: auto;\n border-radius: 5px;\n margin-bottom: 10px;\n display: block;\n }\n\n .storysplat-hotspot-popup.fullscreen img {\n width: auto !important;\n height: auto !important;\n max-width: 90% !important;\n max-height: 100% !important;\n object-fit: contain !important;\n border-radius: 8px;\n margin: 10px 0;\n flex-shrink: 1;\n min-height: 0;\n }\n\n .storysplat-hotspot-popup iframe {\n width: 100%;\n max-width: 100%;\n aspect-ratio: 16 / 9;\n height: auto;\n border: none;\n border-radius: 5px;\n margin-bottom: 10px;\n }\n\n .storysplat-hotspot-popup.fullscreen iframe {\n width: 90% !important;\n max-width: 800px !important;\n height: auto !important;\n aspect-ratio: 16 / 9 !important;\n max-height: 100% !important;\n margin: 10px 0;\n border-radius: 8px;\n flex-shrink: 1;\n min-height: 0;\n }\n\n /* Portal confirmation popup */\n .storysplat-portal-popup {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: rgba(0, 0, 0, 0.85);\n color: white;\n padding: 20px;\n border-radius: 10px;\n z-index: 100002;\n display: none;\n flex-direction: column;\n gap: 15px;\n min-width: 240px;\n max-width: 80%;\n text-align: center;\n box-shadow: 0 0 12px rgba(0, 0, 0, 0.6);\n }\n\n .storysplat-portal-popup.visible {\n display: flex;\n }\n\n .storysplat-portal-popup-title {\n font-size: 16px;\n font-weight: 600;\n }\n\n .storysplat-portal-popup-actions {\n display: flex;\n justify-content: center;\n gap: 10px;\n }\n\n .storysplat-portal-popup-btn {\n padding: 8px 14px;\n border: none;\n border-radius: 6px;\n cursor: pointer;\n font-size: 13px;\n font-weight: 500;\n }\n\n .storysplat-portal-popup-confirm {\n background: ${uiColor};\n color: white;\n }\n\n .storysplat-portal-popup-cancel {\n background: rgba(255, 255, 255, 0.15);\n color: white;\n }\n\n /* Video container - matches HTML export sizing */\n .storysplat-hotspot-popup .video-container {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 300px;\n max-width: 100%;\n height: auto;\n margin: 10px 0;\n }\n\n .storysplat-hotspot-popup video {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n border-radius: 5px;\n border: 2px solid rgba(255, 255, 255, 0.3);\n }\n\n /* Fullscreen popup video - constrained so title+description+buttons always fit */\n .storysplat-hotspot-popup.fullscreen .video-container {\n width: 90% !important;\n max-width: 800px !important;\n max-height: 100% !important;\n height: auto !important;\n margin: 10px 0 !important;\n flex-shrink: 1;\n min-height: 0;\n }\n\n .storysplat-hotspot-popup.fullscreen video {\n max-width: 100% !important;\n max-height: 100% !important;\n object-fit: contain !important;\n border-radius: 8px;\n }\n\n /* Close button - matching BabylonJS export (green button at bottom) */\n .storysplat-hotspot-popup-close {\n display: inline-block;\n width: auto;\n padding: 10px 20px;\n background-color: #4CAF50;\n border: none;\n color: white;\n cursor: pointer;\n border-radius: 5px;\n margin: 5px 0 0 0;\n font-size: 14px;\n font-weight: 500;\n font-family: system-ui, -apple-system, sans-serif;\n text-align: center;\n transition: background-color 0.2s;\n flex-shrink: 0;\n }\n\n .storysplat-hotspot-popup-close:hover {\n background-color: #45a049;\n }\n\n /* Error Popup - for outdated/broken scenes */\n .storysplat-error-popup {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: rgba(0, 0, 0, 0.95);\n border-radius: 12px;\n padding: 30px 40px;\n max-width: 450px;\n width: 90%;\n text-align: center;\n z-index: 10001;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);\n border: 1px solid rgba(255, 100, 100, 0.3);\n }\n\n .storysplat-error-popup-icon {\n font-size: 48px;\n margin-bottom: 15px;\n }\n\n .storysplat-error-popup-title {\n color: #ff6b6b;\n font-size: 20px;\n font-weight: 600;\n margin: 0 0 15px 0;\n }\n\n .storysplat-error-popup-message {\n color: #ccc;\n font-size: 14px;\n line-height: 1.6;\n margin: 0 0 20px 0;\n }\n\n .storysplat-error-popup-action {\n display: inline-block;\n padding: 12px 24px;\n background: linear-gradient(135deg, #4CAF50, #45a049);\n color: white;\n text-decoration: none;\n border-radius: 6px;\n font-weight: 500;\n font-size: 14px;\n transition: transform 0.2s, box-shadow 0.2s;\n }\n\n .storysplat-error-popup-action:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 12px rgba(76, 175, 80, 0.4);\n }\n\n .storysplat-hotspot-popup-link {\n display: block;\n padding: 8px 16px;\n background: #007bff;\n color: white;\n text-decoration: none;\n border-radius: 4px;\n text-align: center;\n font-weight: 500;\n margin: 0 0 4px 0;\n cursor: pointer;\n transition: background-color 0.2s;\n }\n\n .storysplat-hotspot-popup-link:hover {\n background: #0056b3;\n }\n\n /* Mobile Responsive */\n @media (max-width: 768px) {\n .storysplat-scroll-controls {\n margin-bottom: 10px;\n }\n\n .storysplat-hotspot-popup.fullscreen {\n top: 10px !important;\n left: 10px !important;\n right: 10px !important;\n bottom: 10px !important;\n }\n\n .storysplat-hotspot-popup.fullscreen img {\n max-width: 95% !important;\n }\n\n .storysplat-hotspot-popup.fullscreen .video-container {\n width: 95% !important;\n max-width: 95% !important;\n }\n\n .storysplat-hotspot-popup.fullscreen iframe {\n width: 95% !important;\n max-width: 95% !important;\n }\n\n .storysplat-hotspot-popup-title {\n font-size: 16px !important;\n padding: 10px 15px !important;\n }\n }\n\n @media (max-width: 540px) {\n .storysplat-scroll-controls {\n margin-bottom: 45px;\n }\n\n .storysplat-hotspot-popup.fullscreen img {\n max-width: 98% !important;\n }\n\n .storysplat-hotspot-popup.fullscreen .video-container {\n width: 98% !important;\n max-width: 98% !important;\n }\n\n .storysplat-hotspot-popup.fullscreen iframe {\n width: 98% !important;\n max-width: 98% !important;\n }\n }\n\n /* Virtual Joystick Overlay - visual indicator only, touches pass through to canvas */\n .storysplat-joystick-container {\n position: absolute;\n bottom: 80px;\n left: 40px;\n width: 120px;\n height: 120px;\n z-index: 2000;\n pointer-events: none;\n touch-action: none;\n display: none;\n }\n\n .storysplat-joystick-container.visible {\n display: block;\n }\n\n .storysplat-joystick-base {\n position: absolute;\n top: 0;\n left: 0;\n width: 120px;\n height: 120px;\n background: rgba(255, 255, 255, 0.2);\n border: 3px solid rgba(255, 255, 255, 0.4);\n border-radius: 50%;\n pointer-events: none;\n }\n\n .storysplat-joystick-thumb {\n position: absolute;\n top: 50%;\n left: 50%;\n width: 50px;\n height: 50px;\n margin-left: -25px;\n margin-top: -25px;\n background: rgba(255, 255, 255, 0.6);\n border: 3px solid rgba(255, 255, 255, 0.8);\n border-radius: 50%;\n pointer-events: none;\n will-change: transform;\n }\n\n .storysplat-joystick-thumb.active {\n background: rgba(255, 255, 255, 0.8);\n border-color: white;\n }\n\n /* Look Zone Indicator (right side) */\n .storysplat-look-zone {\n position: absolute;\n bottom: 80px;\n right: 40px;\n width: 100px;\n height: 100px;\n z-index: 1000;\n pointer-events: none;\n display: none;\n }\n\n .storysplat-look-zone.visible {\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .storysplat-look-zone-icon {\n width: 60px;\n height: 60px;\n background: rgba(255, 255, 255, 0.15);\n border: 2px solid rgba(255, 255, 255, 0.25);\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .storysplat-look-zone-icon svg {\n width: 30px;\n height: 30px;\n fill: rgba(255, 255, 255, 0.5);\n }\n\n /* Look Zone Active State */\n .storysplat-look-zone.active .storysplat-look-zone-icon {\n background: rgba(255, 255, 255, 0.25);\n border-color: rgba(255, 255, 255, 0.5);\n }\n\n .storysplat-look-zone.active .storysplat-look-zone-icon svg {\n fill: rgba(255, 255, 255, 0.8);\n }\n\n /* Lazy Load Container */\n .storysplat-lazy-load-container {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n justify-content: center;\n align-items: center;\n background: #1a1a1a;\n z-index: 10001;\n }\n\n .storysplat-lazy-load-thumbnail {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n opacity: 0.7;\n }\n\n .storysplat-lazy-load-thumbnail-video {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n opacity: 0.7;\n }\n\n .storysplat-lazy-load-overlay {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: linear-gradient(\n to bottom,\n rgba(0, 0, 0, 0.3) 0%,\n rgba(0, 0, 0, 0.5) 50%,\n rgba(0, 0, 0, 0.7) 100%\n );\n }\n\n .storysplat-lazy-load-content {\n position: relative;\n z-index: 1;\n text-align: center;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n }\n\n .storysplat-lazy-load-start-btn {\n padding: 16px 32px;\n background: ${uiColor};\n border: none;\n border-radius: 50px;\n color: white;\n font-size: 18px;\n font-weight: 600;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 12px;\n transition: all 0.3s ease;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n }\n\n .storysplat-lazy-load-start-btn:hover {\n transform: scale(1.05);\n box-shadow: 0 6px 30px rgba(0, 0, 0, 0.4);\n }\n\n .storysplat-lazy-load-start-btn:active {\n transform: scale(0.98);\n }\n\n .storysplat-lazy-load-start-btn svg {\n width: 24px;\n height: 24px;\n fill: white;\n }\n\n @media (max-width: 768px) {\n .storysplat-lazy-load-start-btn {\n padding: 12px 24px;\n font-size: 16px;\n }\n }\n\n /* Waypoint List Dropdown - Glassmorphic style */\n .storysplat-waypoint-list-container {\n position: absolute;\n top: 10px;\n right: 45px;\n z-index: 1000;\n }\n\n .storysplat-waypoint-list-toggle {\n padding: 8px 16px;\n background: rgba(0, 0, 0, 0.5);\n border: none;\n border-radius: 8px;\n color: white;\n cursor: pointer;\n font-size: 14px;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n transition: background-color 0.3s ease;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .storysplat-waypoint-list-toggle:hover {\n background: rgba(0, 0, 0, 0.7);\n }\n\n .storysplat-waypoint-list-toggle svg {\n width: 16px;\n height: 16px;\n fill: white;\n transition: transform 0.3s ease;\n }\n\n .storysplat-waypoint-list-toggle.open svg {\n transform: rotate(180deg);\n }\n\n .storysplat-waypoint-list-dropdown {\n position: absolute;\n top: 100%;\n right: 0;\n margin-top: 8px;\n min-width: 200px;\n max-height: 300px;\n overflow-y: auto;\n background: rgba(0, 0, 0, 0.8);\n border-radius: 8px;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n display: none;\n flex-direction: column;\n }\n\n .storysplat-waypoint-list-dropdown.open {\n display: flex;\n }\n\n .storysplat-waypoint-item {\n padding: 12px 16px;\n color: white;\n cursor: pointer;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n transition: background-color 0.2s ease;\n font-size: 14px;\n }\n\n .storysplat-waypoint-item:last-child {\n border-bottom: none;\n }\n\n .storysplat-waypoint-item:hover {\n background: rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-waypoint-item.active {\n background: rgba(76, 175, 80, 0.3);\n }\n\n /* Adjust position when fullscreen button is present */\n .storysplat-waypoint-list-container.with-fullscreen {\n right: 45px;\n }\n\n .storysplat-waypoint-list-container.no-fullscreen {\n right: 10px;\n }\n\n @media (max-width: 768px) {\n .storysplat-waypoint-list-container {\n right: 40px;\n }\n\n .storysplat-waypoint-list-toggle {\n padding: 6px 12px;\n font-size: 12px;\n }\n\n .storysplat-waypoint-list-dropdown {\n min-width: 160px;\n max-height: 250px;\n }\n\n .storysplat-waypoint-item {\n padding: 10px 12px;\n font-size: 12px;\n }\n }\n\n /* Watermark - matches BabylonJS HTML export */\n .storysplat-watermark {\n position: absolute;\n bottom: 10px;\n right: 10px;\n background-color: rgba(0, 0, 0, 0.5);\n color: white;\n padding: 5px 10px;\n border-radius: 5px;\n font-size: 12px;\n z-index: 1000;\n pointer-events: auto; /* Allow pointer events for the entire watermark so links work */\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n cursor: default;\n }\n\n .storysplat-watermark a {\n color: ${uiColor};\n text-decoration: none;\n cursor: pointer;\n }\n\n .storysplat-watermark a:hover {\n text-decoration: underline;\n }\n\n .storysplat-fps-counter {\n position: absolute;\n top: 8px;\n left: 8px;\n background: rgba(0, 0, 0, 0.6);\n color: #0f0;\n padding: 4px 8px;\n border-radius: 4px;\n font-size: 12px;\n font-family: monospace;\n z-index: 1000;\n pointer-events: none;\n line-height: 1;\n }\n ` + generateTemplateOverrides(uiColor, template);\n}\n\n/**\n * Generate template-specific CSS overrides.\n * These are appended after the minimal base styles with higher cascade priority.\n */\nfunction generateTemplateOverrides(uiColor: string, template: string): string {\n if (template === 'standard') {\n return `\n /* ===== STANDARD (Centered) Template Overrides ===== */\n .storysplat-scroll-controls {\n width: auto;\n max-width: 350px;\n padding: 15px 20px;\n background: rgba(0, 0, 0, 0.7);\n border-radius: 10px;\n bottom: 20px;\n gap: 8px;\n }\n\n .storysplat-scroll-content {\n gap: 8px;\n }\n\n .storysplat-progress-container {\n max-width: 300px;\n height: 10px;\n border-radius: 5px;\n }\n\n .storysplat-progress-bar {\n border-radius: 5px;\n }\n\n .storysplat-progress-text {\n font-size: 16px;\n }\n\n .storysplat-scroll-buttons {\n gap: 8px;\n }\n\n .storysplat-btn {\n background: ${uiColor};\n padding: 10px 20px;\n font-size: 16px;\n border-radius: 5px;\n }\n\n .storysplat-btn:hover {\n opacity: 0.85;\n background: ${uiColor};\n }\n\n .storysplat-btn-play {\n padding: 10px 12px;\n background: ${uiColor};\n border-radius: 5px;\n }\n\n .storysplat-btn-play svg {\n width: 16px;\n height: 16px;\n }\n\n .storysplat-btn-play:hover {\n opacity: 0.85;\n background: ${uiColor};\n }\n\n .storysplat-mode-container {\n margin-top: 8px;\n }\n\n .storysplat-mode-toggle {\n gap: 8px;\n }\n\n .storysplat-mode-btn {\n padding: 6px 14px;\n font-size: 14px;\n border-radius: 4px;\n border: 1px solid rgba(255, 255, 255, 0.2);\n }\n\n .storysplat-explore-btn {\n padding: 8px 16px;\n font-size: 14px;\n border-radius: 4px;\n }\n\n @media (max-width: 768px) {\n .storysplat-scroll-controls {\n max-width: 280px;\n padding: 12px 15px;\n }\n\n .storysplat-btn {\n padding: 8px 16px;\n font-size: 14px;\n }\n\n .storysplat-progress-container {\n height: 8px;\n }\n\n .storysplat-progress-text {\n font-size: 14px;\n }\n }\n `;\n }\n\n if (template === 'pro') {\n return `\n /* ===== PRO (Dark) Template Overrides ===== */\n .storysplat-scroll-controls {\n width: 80%;\n max-width: 600px;\n padding: 12px 20px;\n background: rgba(0, 0, 0, 0.5);\n border-radius: 8px;\n bottom: 20px;\n left: 50%;\n transform: translateX(-50%);\n gap: 6px;\n }\n\n .storysplat-scroll-content {\n gap: 6px;\n }\n\n .storysplat-progress-container {\n max-width: 100%;\n height: 2px;\n border-radius: 1px;\n }\n\n .storysplat-progress-bar {\n border-radius: 1px;\n }\n\n .storysplat-progress-text {\n font-size: 13px;\n opacity: 0.8;\n }\n\n .storysplat-scroll-buttons {\n gap: 6px;\n }\n\n .storysplat-btn {\n background: rgba(0, 0, 0, 0.5);\n padding: 8px 16px;\n font-size: 14px;\n border-radius: 4px;\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-btn:hover {\n background: rgba(0, 0, 0, 0.7);\n border-color: rgba(255, 255, 255, 0.2);\n }\n\n .storysplat-btn-play {\n padding: 8px 10px;\n background: rgba(0, 0, 0, 0.5);\n border-radius: 4px;\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-btn-play svg {\n width: 14px;\n height: 14px;\n }\n\n .storysplat-btn-play:hover {\n background: rgba(0, 0, 0, 0.7);\n border-color: rgba(255, 255, 255, 0.2);\n }\n\n /* Pro: mode toggle on the left side, vertical stack */\n .storysplat-mode-container {\n position: absolute;\n bottom: 20px;\n left: 20px;\n margin-top: 0;\n z-index: 1000;\n }\n\n .storysplat-mode-toggle {\n flex-direction: column;\n gap: 4px;\n }\n\n .storysplat-mode-btn {\n padding: 6px 12px;\n font-size: 13px;\n border-radius: 4px;\n background: rgba(0, 0, 0, 0.5);\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-mode-btn:hover {\n background: rgba(0, 0, 0, 0.7);\n border-color: rgba(255, 255, 255, 0.2);\n }\n\n .storysplat-explore-controls {\n flex-direction: column;\n }\n\n .storysplat-explore-btn {\n padding: 6px 14px;\n font-size: 13px;\n border-radius: 4px;\n background: rgba(0, 0, 0, 0.5);\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-explore-btn:hover {\n background: rgba(0, 0, 0, 0.7);\n }\n\n @media (max-width: 768px) {\n .storysplat-scroll-controls {\n width: 70%;\n max-width: none;\n padding: 10px 15px;\n bottom: 10px;\n }\n\n .storysplat-mode-container {\n bottom: 10px;\n left: 10px;\n }\n\n .storysplat-btn {\n padding: 6px 12px;\n font-size: 12px;\n }\n }\n `;\n }\n\n // 'minimal' (default) — no overrides needed\n return '';\n}\n/**\n * Inject CSS styles into the document\n */\nexport function injectStyles(uiColor: string = '#4CAF50', template: string = 'minimal'): HTMLStyleElement {\n const existingStyle = document.getElementById('storysplat-viewer-styles');\n if (existingStyle) {\n existingStyle.remove();\n }\n const style = document.createElement('style');\n style.id = 'storysplat-viewer-styles';\n style.textContent = generateViewerStyles(uiColor, template);\n document.head.appendChild(style);\n return style;\n}\n// StorySplat default assets\nconst STORYSPLAT_LOGO_URL = 'https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fimages%2FStorySplat.webp?alt=media&token=953e8ab3-1865-4ac1-a98d-b548b7066bda';\nconst STORYSPLAT_LOTTIE_URL = 'https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Flotties%2FstorySplatLottie.json?alt=media&token=d7edc19d-9cb8-4c6e-a94c-cba1d2b65d5e';\n/**\n * Inject Lottie player script if not already loaded\n */\nfunction ensureLottiePlayer(): Promise<void> {\n return new Promise((resolve) => {\n // Check if already loaded\n if (customElements.get('lottie-player')) {\n resolve();\n return;\n }\n // Check if script is already being loaded\n const existingScript = document.querySelector('script[src*=\"lottie-player\"]');\n if (existingScript) {\n existingScript.addEventListener('load', () => resolve());\n return;\n }\n // Load the script\n const script = document.createElement('script');\n script.src = 'https://unpkg.com/@lottiefiles/lottie-player@latest/dist/lottie-player.js';\n script.onload = () => resolve();\n document.head.appendChild(script);\n });\n}\n/**\n * Create preloader element\n */\nexport function createPreloader(container: HTMLElement, customLogoUrl?: string, buttonLabels?: ButtonLabels): HTMLElement {\n const preloader = document.createElement('div');\n preloader.className = 'storysplat-preloader';\n const useCustomLogo = !!customLogoUrl;\n preloader.innerHTML = `\n <div class=\"storysplat-preloader-content\">\n <div class=\"storysplat-preloader-media\">\n ${useCustomLogo\n ? `<img class=\"storysplat-preloader-image\" src=\"${customLogoUrl}\" alt=\"Custom Logo\" />`\n : `<img class=\"storysplat-preloader-image-inverted\" src=\"${STORYSPLAT_LOGO_URL}\" alt=\"StorySplat Logo\" />`}\n ${!useCustomLogo\n ? `<lottie-player class=\"storysplat-preloader-lottie\"\n src=\"${STORYSPLAT_LOTTIE_URL}\"\n background=\"transparent\"\n speed=\"1\"\n loop\n autoplay>\n </lottie-player>`\n : ''}\n </div>\n <div class=\"storysplat-preloader-progress\">\n <div class=\"storysplat-preloader-text\">${getButtonLabel(buttonLabels, 'loading')} 0%</div>\n <div class=\"storysplat-preloader-bar\"></div>\n </div>\n </div>\n `;\n container.appendChild(preloader);\n // Load Lottie player script if using default logo\n if (!useCustomLogo) {\n ensureLottiePlayer();\n }\n return preloader;\n}\n/**\n * Update preloader progress\n */\nexport function updatePreloaderProgress(preloader: HTMLElement, progress: number, text?: string, loadingLabel?: string): void {\n const bar = preloader.querySelector('.storysplat-preloader-bar') as HTMLElement;\n const textEl = preloader.querySelector('.storysplat-preloader-text') as HTMLElement;\n const percent = Math.max(0, Math.min(100, progress * 100));\n if (bar)\n bar.style.width = `${percent}%`;\n if (textEl)\n textEl.textContent = text || `${loadingLabel || DEFAULT_BUTTON_LABELS.loading} ${Math.round(percent)}%`;\n}\n/**\n * Hide preloader\n */\nexport function hidePreloader(preloader: HTMLElement): void {\n preloader.classList.add('hidden');\n setTimeout(() => preloader.remove(), 500);\n}\n/**\n * Show error popup for load failures (e.g., outdated scenes that need re-export)\n */\nexport function showErrorPopup(container: HTMLElement, errorMessage: string): void {\n // Remove any existing error popup\n container.querySelector('.storysplat-error-popup')?.remove();\n // Also hide the preloader if visible\n const preloader = container.querySelector('.storysplat-preloader') as HTMLElement;\n if (preloader) {\n hidePreloader(preloader);\n }\n // Determine if this is an outdated scene error\n const isOutdatedScene = errorMessage.includes('Failed to load splat from any URL');\n const popup = document.createElement('div');\n popup.className = 'storysplat-error-popup';\n if (isOutdatedScene) {\n popup.innerHTML = `\n <div class=\"storysplat-error-popup-icon\">⚠️</div>\n <h3 class=\"storysplat-error-popup-title\">Scene Needs Update</h3>\n <p class=\"storysplat-error-popup-message\">\n This scene was created with an older version of StorySplat and needs to be re-exported to work with the latest viewer.\n <br><br>\n Please ask the scene creator to re-export it from the StorySplat editor.\n </p>\n <a href=\"https://storysplat.com\" target=\"_blank\" class=\"storysplat-error-popup-action\">\n Visit StorySplat\n </a>\n `;\n }\n else {\n popup.innerHTML = `\n <div class=\"storysplat-error-popup-icon\">❌</div>\n <h3 class=\"storysplat-error-popup-title\">Failed to Load Scene</h3>\n <p class=\"storysplat-error-popup-message\">\n ${errorMessage || 'An error occurred while loading this scene. Please try refreshing the page.'}\n </p>\n `;\n }\n container.appendChild(popup);\n}\n/**\n * Create UI elements for the viewer\n */\nexport function createUIElements(container: HTMLElement, config: ViewerConfig, options: UIOptions = {}): UIElements {\n const { uiColor = '#4CAF50', showScrollControls = true, showModeToggle = true, showFullscreenButton = true, showHelpButton = false, // Minimal: hide by default\n showPreloader = true, allowedCameraModes = ['tour', 'explore'], defaultCameraMode = 'tour', customPreloaderLogoUrl, buttonLabels, hideWatermark = false, watermarkText, watermarkLink, sceneId, showWaypointList = true, template = 'minimal' } = options;\n // Get localized labels\n const labels = {\n tour: getButtonLabel(buttonLabels, 'tour'),\n explore: getButtonLabel(buttonLabels, 'explore'),\n walk: getButtonLabel(buttonLabels, 'walk'),\n orbit: getButtonLabel(buttonLabels, 'orbit'),\n fly: getButtonLabel(buttonLabels, 'fly'),\n previous: getButtonLabel(buttonLabels, 'previous'),\n next: getButtonLabel(buttonLabels, 'next'),\n fullscreen: getButtonLabel(buttonLabels, 'fullscreen'),\n waypoints: getButtonLabel(buttonLabels, 'waypoints'),\n close: getButtonLabel(buttonLabels, 'close'),\n yes: getButtonLabel(buttonLabels, 'yes'),\n cancel: getButtonLabel(buttonLabels, 'cancel'),\n vr: getButtonLabel(buttonLabels, 'vr'),\n ar: getButtonLabel(buttonLabels, 'ar'),\n loading: getButtonLabel(buttonLabels, 'loading'),\n helpTitle: getButtonLabel(buttonLabels, 'helpTitle'),\n helpCameraModes: getButtonLabel(buttonLabels, 'helpCameraModes'),\n helpTourDesc: getButtonLabel(buttonLabels, 'helpTourDesc'),\n helpExploreDesc: getButtonLabel(buttonLabels, 'helpExploreDesc'),\n helpWalkDesc: getButtonLabel(buttonLabels, 'helpWalkDesc'),\n hotspotDefaultTitle: getButtonLabel(buttonLabels, 'hotspotDefaultTitle'),\n openExternalLink: getButtonLabel(buttonLabels, 'openExternalLink'),\n };\n const elements: UIElements = {};\n // Clean up any existing UI elements from previous renders\n container.querySelectorAll('.storysplat-preloader, .storysplat-scroll-controls, .storysplat-waypoint-info, .storysplat-fullscreen-btn, .storysplat-help-btn, .storysplat-help-panel, .storysplat-watermark, .storysplat-waypoint-list-container, .storysplat-hotspot-popup, .storysplat-portal-popup').forEach(el => el.remove());\n // Inject styles (template-aware)\n injectStyles(uiColor, template);\n // Add container class\n container.classList.add('storysplat-viewer-container');\n // Create Preloader\n if (showPreloader) {\n elements.preloader = createPreloader(container, customPreloaderLogoUrl, buttonLabels);\n }\n // Create Waypoint Info Panel (top banner style)\n const waypointInfo = document.createElement('div');\n waypointInfo.className = 'storysplat-waypoint-info';\n waypointInfo.innerHTML = `\n <h2 class=\"storysplat-waypoint-title\"></h2>\n <p class=\"storysplat-waypoint-description\"></p>\n `;\n container.appendChild(waypointInfo);\n elements.waypointInfo = waypointInfo;\n // Create Scroll Controls\n if (showScrollControls && config.waypoints && config.waypoints.length > 0) {\n const scrollControls = document.createElement('div');\n scrollControls.className = 'storysplat-scroll-controls';\n scrollControls.innerHTML = `\n <div class=\"storysplat-scroll-content\">\n <div class=\"storysplat-progress-text\">0%</div>\n <div class=\"storysplat-progress-container\">\n <div class=\"storysplat-progress-bar\"></div>\n </div>\n <div class=\"storysplat-scroll-buttons\">\n <button class=\"storysplat-btn storysplat-btn-prev\">${labels.previous}</button>\n <button class=\"storysplat-btn storysplat-btn-play\">\n <svg viewBox=\"0 0 24 24\"><path d=\"M8 5v14l11-7z\"/></svg>\n </button>\n <button class=\"storysplat-btn storysplat-btn-next\">${labels.next}</button>\n </div>\n <div class=\"storysplat-explore-controls\">\n <button class=\"storysplat-explore-btn\" data-explore-mode=\"orbit\">${labels.orbit}</button>\n <button class=\"storysplat-explore-btn\" data-explore-mode=\"fly\">${labels.fly}</button>\n </div>\n ${showModeToggle && allowedCameraModes.length > 1 ? `\n <div class=\"storysplat-mode-container\">\n <div class=\"storysplat-mode-toggle\">\n ${allowedCameraModes.includes('tour') ? `<button class=\"storysplat-mode-btn ${defaultCameraMode === 'tour' ? 'selected' : ''}\" data-mode=\"tour\">${labels.tour}</button>` : ''}\n ${allowedCameraModes.includes('explore') ? `<button class=\"storysplat-mode-btn ${defaultCameraMode === 'explore' ? 'selected' : ''}\" data-mode=\"explore\">${labels.explore}</button>` : ''}\n ${allowedCameraModes.includes('walk') ? `<button class=\"storysplat-mode-btn ${defaultCameraMode === 'walk' ? 'selected' : ''}\" data-mode=\"walk\">${labels.walk}</button>` : ''}\n </div>\n </div>\n ` : ''}\n </div>\n `;\n container.appendChild(scrollControls);\n elements.scrollControls = scrollControls;\n elements.progressBar = scrollControls.querySelector('.storysplat-progress-bar') as HTMLElement;\n elements.progressText = scrollControls.querySelector('.storysplat-progress-text') as HTMLElement;\n // Pro template: move mode container out of scroll controls to viewer root\n // so it can be positioned independently on the left side via CSS\n if (template === 'pro') {\n const modeContainer = scrollControls.querySelector('.storysplat-mode-container');\n if (modeContainer) {\n container.appendChild(modeContainer);\n }\n }\n }\n // Create Fullscreen Button\n if (showFullscreenButton) {\n const fullscreenBtn = document.createElement('button');\n fullscreenBtn.className = 'storysplat-fullscreen-btn';\n fullscreenBtn.setAttribute('aria-label', labels.fullscreen);\n fullscreenBtn.innerHTML = `\n <svg class=\"storysplat-expand-icon\" viewBox=\"0 0 24 24\">\n <path d=\"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z\"/>\n </svg>\n <svg class=\"storysplat-compress-icon\" viewBox=\"0 0 24 24\" style=\"display: none;\">\n <path d=\"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z\"/>\n </svg>\n `;\n container.appendChild(fullscreenBtn);\n elements.fullscreenButton = fullscreenBtn;\n }\n // Create Waypoint List Dropdown (for quick navigation to waypoints)\n if (showWaypointList && config.waypoints && config.waypoints.length > 0) {\n const waypointListContainer = document.createElement('div');\n waypointListContainer.className = `storysplat-waypoint-list-container ${showFullscreenButton ? 'with-fullscreen' : 'no-fullscreen'}`;\n // Build waypoint items HTML\n const waypointItemsHtml = config.waypoints.map((wp, index) => {\n const waypointName = wp.name || `Waypoint ${index + 1}`;\n return `<div class=\"storysplat-waypoint-item\" data-waypoint-index=\"${index}\">${waypointName}</div>`;\n }).join('');\n waypointListContainer.innerHTML = `\n <button class=\"storysplat-waypoint-list-toggle\" aria-label=\"${labels.waypoints}\">\n ${labels.waypoints}\n <svg viewBox=\"0 0 24 24\">\n <path d=\"M7 10l5 5 5-5z\"/>\n </svg>\n </button>\n <div class=\"storysplat-waypoint-list-dropdown\">\n ${waypointItemsHtml}\n </div>\n `;\n container.appendChild(waypointListContainer);\n elements.waypointListContainer = waypointListContainer;\n // Setup toggle behavior\n const toggleBtn = waypointListContainer.querySelector('.storysplat-waypoint-list-toggle');\n const dropdown = waypointListContainer.querySelector('.storysplat-waypoint-list-dropdown');\n toggleBtn?.addEventListener('click', (e) => {\n e.stopPropagation();\n toggleBtn.classList.toggle('open');\n dropdown?.classList.toggle('open');\n });\n // Close dropdown when clicking outside\n document.addEventListener('click', (e) => {\n if (!waypointListContainer.contains(e.target as Node)) {\n toggleBtn?.classList.remove('open');\n dropdown?.classList.remove('open');\n }\n });\n }\n // Create VR Button (hidden by default, shown when VR is available)\n const vrBtn = document.createElement('button');\n vrBtn.className = 'storysplat-xr-btn storysplat-vr-btn';\n vrBtn.setAttribute('aria-label', labels.vr);\n vrBtn.textContent = labels.vr;\n container.appendChild(vrBtn);\n elements.vrButton = vrBtn;\n // Create AR Button (hidden by default, shown when AR is available)\n const arBtn = document.createElement('button');\n arBtn.className = 'storysplat-xr-btn storysplat-ar-btn';\n arBtn.setAttribute('aria-label', labels.ar);\n arBtn.textContent = labels.ar;\n container.appendChild(arBtn);\n elements.arButton = arBtn;\n // Create Help Button and Panel (optional in minimal)\n if (showHelpButton) {\n const helpBtn = document.createElement('button');\n helpBtn.className = 'storysplat-help-btn';\n helpBtn.setAttribute('title', labels.helpTitle);\n helpBtn.textContent = '?';\n container.appendChild(helpBtn);\n elements.helpButton = helpBtn;\n const helpPanel = document.createElement('div');\n helpPanel.className = 'storysplat-help-panel';\n helpPanel.innerHTML = `\n <h3>${labels.helpTitle}</h3>\n <p><strong>${labels.helpCameraModes}</strong></p>\n <p>• ${labels.tour} - ${labels.helpTourDesc}</p>\n <p>• ${labels.explore} - ${labels.helpExploreDesc}</p>\n <p>• ${labels.walk} - ${labels.helpWalkDesc}</p>\n <br/>\n <p><strong>${labels.tour} Mode:</strong></p>\n <p>• Scroll - Move along path</p>\n <p>• Drag - Look around</p>\n <br/>\n <p><strong>${labels.explore} Mode:</strong></p>\n <p>• LMB Drag - Orbit camera</p>\n <p>• RMB Drag - Fly/look</p>\n <p>• WASD/QE - Move camera</p>\n <p>• Shift - Move fast</p>\n <p>• Scroll/Pinch - Zoom</p>\n <p>• Double-click - Focus</p>\n <br/>\n <p><strong>${labels.walk} Mode:</strong></p>\n <p>• Click to lock mouse</p>\n <p>• WASD/Arrows - Move</p>\n <p>• Mouse - Look around</p>\n <p>• Shift - Sprint</p>\n <p>• Space - Jump</p>\n `;\n container.appendChild(helpPanel);\n elements.helpPanel = helpPanel;\n }\n // Create Hotspot Popup (always created, shown when needed)\n // Note: No overlay - matching BabylonJS export behavior\n const hotspotPopup = document.createElement('div');\n hotspotPopup.className = 'storysplat-hotspot-popup';\n hotspotPopup.id = 'hotspotContent'; // Match BabylonJS export ID\n hotspotPopup.innerHTML = `\n <h2 class=\"storysplat-hotspot-popup-title\"></h2>\n <div class=\"storysplat-hotspot-popup-content\"></div>\n <button class=\"storysplat-hotspot-popup-close\">${labels.close}</button>\n `;\n container.appendChild(hotspotPopup);\n elements.hotspotPopup = hotspotPopup;\n // Close popup on close button click\n const closeBtn = hotspotPopup.querySelector('.storysplat-hotspot-popup-close');\n closeBtn?.addEventListener('click', () => {\n hotspotPopup.classList.remove('visible', 'fullscreen');\n });\n // Create Portal Confirmation Popup\n const portalPopup = document.createElement('div');\n portalPopup.className = 'storysplat-portal-popup';\n portalPopup.innerHTML = `\n <div class=\"storysplat-portal-popup-title\"></div>\n <div class=\"storysplat-portal-popup-actions\">\n <button class=\"storysplat-portal-popup-btn storysplat-portal-popup-confirm\">${labels.yes}</button>\n <button class=\"storysplat-portal-popup-btn storysplat-portal-popup-cancel\">${labels.cancel}</button>\n </div>\n `;\n container.appendChild(portalPopup);\n elements.portalPopup = portalPopup;\n // Create Virtual Joystick (for mobile explore mode)\n const joystick = document.createElement('div');\n joystick.className = 'storysplat-joystick-container';\n joystick.innerHTML = `\n <div class=\"storysplat-joystick-base\"></div>\n <div class=\"storysplat-joystick-thumb\"></div>\n `;\n container.appendChild(joystick);\n elements.joystick = joystick;\n elements.joystickThumb = joystick.querySelector('.storysplat-joystick-thumb') as HTMLElement;\n // Create Look Zone indicator (right side)\n const lookZone = document.createElement('div');\n lookZone.className = 'storysplat-look-zone';\n lookZone.innerHTML = `\n <div class=\"storysplat-look-zone-icon\">\n <svg viewBox=\"0 0 24 24\">\n <path d=\"M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z\"/>\n </svg>\n </div>\n `;\n container.appendChild(lookZone);\n elements.lookZone = lookZone;\n // Create Watermark (unless hidden for whitelabeling)\n if (!hideWatermark) {\n const watermark = document.createElement('div');\n watermark.className = 'storysplat-watermark';\n // Build watermark content - matches BabylonJS HTML export behavior\n const defaultWatermarkLink = sceneId\n ? `https://storysplat.com?ref=${sceneId}`\n : 'https://storysplat.com';\n const finalLink = watermarkLink || defaultWatermarkLink;\n if (watermarkText) {\n // Custom text provided (whitelabeling)\n watermark.innerHTML = `<a href=\"${finalLink}\" target=\"_blank\">${watermarkText}</a>`;\n }\n else {\n // Default StorySplat watermark\n watermark.innerHTML = `Created with <a href=\"${finalLink}\" target=\"_blank\">StorySplat</a>`;\n }\n container.appendChild(watermark);\n elements.watermark = watermark;\n }\n // Create FPS counter (debug mode)\n if (options.debugMode) {\n const fpsCounter = document.createElement('div');\n fpsCounter.className = 'storysplat-fps-counter';\n fpsCounter.textContent = '-- FPS';\n container.appendChild(fpsCounter);\n elements.fpsCounter = fpsCounter;\n }\n return elements;\n}\n/**\n * Update the FPS counter display\n */\nexport function updateFpsCounter(elements: UIElements, fps: number): void {\n if (elements.fpsCounter) {\n elements.fpsCounter.textContent = `${Math.round(fps)} FPS`;\n }\n}\n/**\n * Connect UI elements to viewer controls\n */\nexport function connectUIToViewer(elements: UIElements, viewer: ViewerUIController, defaultCameraMode: string = 'tour', buttonLabels?: ButtonLabels): void {\n // Prev/Next buttons\n const prevBtn = elements.scrollControls?.querySelector('.storysplat-btn-prev');\n const nextBtn = elements.scrollControls?.querySelector('.storysplat-btn-next');\n const playBtn = elements.scrollControls?.querySelector('.storysplat-btn-play');\n if (prevBtn) {\n prevBtn.addEventListener('click', () => viewer.prevWaypoint());\n }\n if (nextBtn) {\n nextBtn.addEventListener('click', () => viewer.nextWaypoint());\n }\n if (playBtn) {\n // Update button icon based on state\n const updatePlayButtonIcon = (isPlaying: boolean) => {\n if (isPlaying) {\n playBtn.innerHTML = '<svg viewBox=\"0 0 24 24\"><path d=\"M6 4h4v16H6zm8 0h4v16h-4z\"/></svg>'; // Pause icon\n }\n else {\n playBtn.innerHTML = '<svg viewBox=\"0 0 24 24\"><path d=\"M8 5v14l11-7z\"/></svg>'; // Play icon\n }\n };\n playBtn.addEventListener('click', () => {\n if (viewer.isPlaying()) {\n viewer.pause();\n }\n else {\n viewer.play();\n }\n // Icon will be updated by event listeners below\n });\n // Listen for playback events to update button state (handles autoplay and programmatic play/pause)\n viewer.on('playbackStart', () => updatePlayButtonIcon(true));\n viewer.on('playbackStop', () => updatePlayButtonIcon(false));\n // Set initial state based on current playing status\n updatePlayButtonIcon(viewer.isPlaying());\n }\n // Help button\n if (elements.helpButton && elements.helpPanel) {\n elements.helpButton.addEventListener('click', () => {\n elements.helpPanel!.classList.toggle('visible');\n });\n }\n // Fullscreen button\n if (elements.fullscreenButton) {\n // Detect iOS - iOS does not support the Fullscreen API\n const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) ||\n (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1); // iPad Pro detection\n // Hide fullscreen button on iOS since the API doesn't work there\n if (isIOS) {\n elements.fullscreenButton.style.display = 'none';\n console.log('[StorySplat Viewer] Fullscreen button hidden on iOS (API not supported)');\n }\n else {\n const container = elements.fullscreenButton.parentElement;\n elements.fullscreenButton.addEventListener('click', () => {\n // Use webkit-prefixed API for Safari compatibility\n const doc = document as WebkitFullscreenDocument;\n const fullscreenElement = doc.fullscreenElement || doc.webkitFullscreenElement;\n if (!fullscreenElement) {\n // Enter fullscreen - try standard API first, then webkit\n if (container?.requestFullscreen) {\n container.requestFullscreen();\n }\n else if ((container as WebkitFullscreenElement | null)?.webkitRequestFullscreen) {\n (container as WebkitFullscreenElement).webkitRequestFullscreen?.();\n }\n const expandIcon = elements.fullscreenButton!.querySelector('.storysplat-expand-icon') as HTMLElement;\n const compressIcon = elements.fullscreenButton!.querySelector('.storysplat-compress-icon') as HTMLElement;\n if (expandIcon)\n expandIcon.style.display = 'none';\n if (compressIcon)\n compressIcon.style.display = 'block';\n }\n else {\n // Exit fullscreen - try standard API first, then webkit\n if (doc.exitFullscreen) {\n doc.exitFullscreen();\n }\n else if (doc.webkitExitFullscreen) {\n doc.webkitExitFullscreen();\n }\n const expandIcon = elements.fullscreenButton!.querySelector('.storysplat-expand-icon') as HTMLElement;\n const compressIcon = elements.fullscreenButton!.querySelector('.storysplat-compress-icon') as HTMLElement;\n if (expandIcon)\n expandIcon.style.display = 'block';\n if (compressIcon)\n compressIcon.style.display = 'none';\n }\n });\n // Listen for fullscreen change events (including webkit prefix)\n const handleFullscreenChange = () => {\n const doc = document as WebkitFullscreenDocument;\n const isFullscreen = doc.fullscreenElement || doc.webkitFullscreenElement;\n const expandIcon = elements.fullscreenButton!.querySelector('.storysplat-expand-icon') as HTMLElement;\n const compressIcon = elements.fullscreenButton!.querySelector('.storysplat-compress-icon') as HTMLElement;\n if (expandIcon)\n expandIcon.style.display = isFullscreen ? 'none' : 'block';\n if (compressIcon)\n compressIcon.style.display = isFullscreen ? 'block' : 'none';\n };\n document.addEventListener('fullscreenchange', handleFullscreenChange);\n document.addEventListener('webkitfullscreenchange', handleFullscreenChange);\n }\n }\n // Helper to show/hide tour navigation controls based on mode\n const setTourControlsVisible = (visible: boolean) => {\n const progressText = elements.scrollControls?.querySelector('.storysplat-progress-text') as HTMLElement;\n const progressContainer = elements.scrollControls?.querySelector('.storysplat-progress-container') as HTMLElement;\n const scrollButtons = elements.scrollControls?.querySelector('.storysplat-scroll-buttons') as HTMLElement;\n const display = visible ? '' : 'none';\n if (progressText)\n progressText.style.display = display;\n if (progressContainer)\n progressContainer.style.display = display;\n if (scrollButtons)\n scrollButtons.style.display = display;\n };\n // Helper to show/hide explore controls (orbit/fly buttons)\n const setExploreControlsVisible = (visible: boolean) => {\n const exploreControls = elements.scrollControls?.querySelector('.storysplat-explore-controls') as HTMLElement;\n if (exploreControls) {\n if (visible) {\n exploreControls.classList.add('visible');\n }\n else {\n exploreControls.classList.remove('visible');\n }\n }\n };\n // Helper to update orbit/fly button selected state\n const updateExploreButtons = (activeMode: 'orbit' | 'fly') => {\n const exploreBtns = elements.scrollControls?.querySelectorAll('.storysplat-explore-btn');\n exploreBtns?.forEach(btn => {\n const mode = btn.getAttribute('data-explore-mode');\n btn.classList.toggle('selected', mode === activeMode);\n });\n };\n // Set initial visibility based on default mode\n setTourControlsVisible(defaultCameraMode === 'tour');\n setExploreControlsVisible(defaultCameraMode === 'explore');\n // Mode toggle buttons\n // Query from the container (parent) since pro template moves mode buttons out of scrollControls\n if (viewer.setCameraMode) {\n const modeButtonContainer = elements.scrollControls?.parentElement || elements.scrollControls;\n const modeButtons = modeButtonContainer?.querySelectorAll('.storysplat-mode-btn');\n modeButtons?.forEach(btn => {\n btn.addEventListener('click', () => {\n const mode = btn.getAttribute('data-mode');\n if (mode && viewer.setCameraMode) {\n viewer.setCameraMode(mode);\n // Update selected state\n modeButtons.forEach(b => b.classList.remove('selected'));\n btn.classList.add('selected');\n // Show/hide tour vs explore controls based on mode\n setTourControlsVisible(mode === 'tour');\n setExploreControlsVisible(mode === 'explore');\n }\n });\n });\n // Listen for mode changes\n viewer.on('modeChange', ({ mode }: {\n mode: string;\n }) => {\n setTourControlsVisible(mode === 'tour');\n setExploreControlsVisible(mode === 'explore');\n // Update button selected state\n modeButtons?.forEach(btn => {\n const btnMode = btn.getAttribute('data-mode');\n btn.classList.toggle('selected', btnMode === mode);\n });\n });\n }\n // Update progress continuously (throttled to prevent visual artifacts)\n let lastProgressUpdate = 0;\n let lastDisplayedPercentage = -1;\n viewer.on('progressUpdate', ({ progress }: {\n progress: number;\n }) => {\n // Clamp progress to 0-1 range\n const clampedProgress = Math.max(0, Math.min(1, progress));\n const percentage = clampedProgress * 100;\n const roundedPercentage = Math.round(percentage);\n // Throttle UI updates to ~30fps to prevent visual glitches\n const now = performance.now();\n if (now - lastProgressUpdate < 33)\n return;\n lastProgressUpdate = now;\n if (elements.progressBar) {\n elements.progressBar.style.width = `${percentage}%`;\n }\n // Only update text if the percentage actually changed (prevents flickering)\n if (elements.progressText && roundedPercentage !== lastDisplayedPercentage) {\n lastDisplayedPercentage = roundedPercentage;\n // Clear and set to ensure no duplicate content\n elements.progressText.innerHTML = '';\n elements.progressText.textContent = formatPercentageLabel(buttonLabels, roundedPercentage);\n }\n });\n // Update waypoint info panel when waypoint changes\n let lastDisplayedWaypointIndex = -1;\n viewer.on('waypointChange', ({ index, waypoint }: {\n index: number;\n waypoint?: WaypointData;\n }) => {\n // Only update if waypoint actually changed\n if (index === lastDisplayedWaypointIndex)\n return;\n lastDisplayedWaypointIndex = index;\n if (!elements.waypointInfo)\n return;\n const titleEl = elements.waypointInfo.querySelector('.storysplat-waypoint-title') as HTMLElement;\n const descEl = elements.waypointInfo.querySelector('.storysplat-waypoint-description') as HTMLElement;\n if (!titleEl || !descEl)\n return;\n // Get waypoint data - either from event or from viewer\n const wp = waypoint || (viewer.getWaypoints?.()[index]);\n if (wp && (wp.name || wp.info)) {\n // Show waypoint name as title\n titleEl.textContent = wp.name || '';\n // Show waypoint info as description\n descEl.textContent = wp.info || '';\n // Show panel if there's content\n if (wp.name || wp.info) {\n elements.waypointInfo.classList.add('hasContent');\n }\n else {\n elements.waypointInfo.classList.remove('hasContent');\n }\n }\n else {\n // Hide panel if no content\n titleEl.textContent = '';\n descEl.textContent = '';\n elements.waypointInfo.classList.remove('hasContent');\n }\n });\n}\n/**\n * Show hotspot popup with content - matching BabylonJS HTML export behavior\n */\nexport function showHotspotPopup(container: HTMLElement, hotspot: HotspotData, buttonLabels?: ButtonLabels): void {\n const popup = container.querySelector('.storysplat-hotspot-popup') as HTMLElement;\n if (!popup)\n return;\n const titleEl = popup.querySelector('.storysplat-hotspot-popup-title') as HTMLElement;\n const contentEl = popup.querySelector('.storysplat-hotspot-popup-content') as HTMLElement;\n const closeBtn = popup.querySelector('.storysplat-hotspot-popup-close') as HTMLElement;\n // Reset any previous custom styles and classes\n popup.style.cssText = '';\n popup.classList.remove('fullscreen');\n // Determine activation mode (default to 'click')\n const activationMode = hotspot.activationMode || 'click';\n const hasMediaContent = hotspot.photoUrl || hotspot.popupVideoUrl || (hotspot.contentType === 'iframe' && hotspot.iframeUrl);\n // Apply fullscreen mode for click activation with photo, video, or iframe (matching BabylonJS export)\n if (activationMode === 'click' && hasMediaContent) {\n popup.classList.add('fullscreen');\n }\n // Apply custom styling if provided (matching BabylonJS export)\n // Combine background color with alpha (default 0.75 opacity)\n const bgAlpha = hotspot.backgroundAlpha ?? 0.75;\n if (hotspot.backgroundColor) {\n const hex = hotspot.backgroundColor.replace('#', '');\n if (hex.length === 6) {\n const r = parseInt(hex.substring(0, 2), 16);\n const g = parseInt(hex.substring(2, 4), 16);\n const b = parseInt(hex.substring(4, 6), 16);\n popup.style.backgroundColor = `rgba(${r}, ${g}, ${b}, ${bgAlpha})`;\n } else {\n popup.style.backgroundColor = hotspot.backgroundColor;\n }\n } else {\n // Default black background with custom alpha\n popup.style.backgroundColor = `rgba(0, 0, 0, ${bgAlpha})`;\n }\n if (hotspot.textColor) {\n popup.style.color = hotspot.textColor;\n if (titleEl)\n titleEl.style.color = hotspot.textColor;\n }\n if (hotspot.fontFamily) {\n popup.style.fontFamily = hotspot.fontFamily;\n }\n if (hotspot.fontSize) {\n popup.style.fontSize = `${hotspot.fontSize}px`;\n }\n // Apply close button color if provided\n if (closeBtn && hotspot.closeButtonColor) {\n closeBtn.style.backgroundColor = hotspot.closeButtonColor;\n }\n // Set title\n if (titleEl) {\n titleEl.textContent = hotspot.title || getButtonLabel(buttonLabels, 'hotspotDefaultTitle');\n }\n // Build content HTML - order matches BabylonJS export: title, iframe, photo, info, link\n let contentHtml = '';\n // Iframe content (before photo in BabylonJS export)\n if (hotspot.contentType === 'iframe' && hotspot.iframeUrl) {\n contentHtml += `<iframe src=\"${hotspot.iframeUrl}\" title=\"${hotspot.title || 'Embedded content'}\"></iframe>`;\n }\n // Video content (embedded video player in popup) - wrapped in container for sizing\n if (hotspot.popupVideoUrl) {\n contentHtml += `<div class=\"video-container\"><video src=\"${hotspot.popupVideoUrl}\" controls playsinline webkit-playsinline preload=\"metadata\"></video></div>`;\n }\n // Image content\n if (hotspot.photoUrl) {\n contentHtml += `<img src=\"${hotspot.photoUrl}\" alt=\"${hotspot.title || 'Hotspot image'}\" />`;\n }\n // Information text\n if (hotspot.information) {\n contentHtml += `<p>${hotspot.information}</p>`;\n }\n // External link\n if (hotspot.externalLinkUrl) {\n const btnColor = hotspot.externalLinkButtonColor || '#007bff';\n contentHtml += `\n <div onclick=\"window.open('${hotspot.externalLinkUrl}', '_blank', 'noopener,noreferrer')\"\n class=\"storysplat-hotspot-popup-link\" style=\"background-color: ${btnColor}\">\n ${hotspot.externalLinkText || getButtonLabel(buttonLabels, 'openExternalLink')}\n </div>\n `;\n }\n if (contentEl) {\n contentEl.innerHTML = contentHtml;\n }\n // Show popup (no overlay - matching BabylonJS export)\n popup.classList.add('visible');\n}\n/**\n * Hide hotspot popup\n */\nexport function hideHotspotPopup(container: HTMLElement): void {\n const popup = container.querySelector('.storysplat-hotspot-popup') as HTMLElement;\n if (popup)\n popup.classList.remove('visible', 'fullscreen');\n}\n/**\n * Show/hide virtual joystick overlay\n */\nexport function setJoystickVisible(elements: UIElements, visible: boolean): void {\n if (elements.joystick) {\n if (visible) {\n elements.joystick.classList.add('visible');\n // Reset thumb position to prevent stuck state\n if (elements.joystickThumb) {\n elements.joystickThumb.classList.remove('active');\n elements.joystickThumb.style.transform = 'translate(0, 0)';\n }\n }\n else {\n elements.joystick.classList.remove('visible');\n }\n }\n if (elements.lookZone) {\n if (visible) {\n elements.lookZone.classList.add('visible');\n }\n else {\n elements.lookZone.classList.remove('visible');\n }\n }\n}\n/**\n * Update joystick thumb position based on touch input\n */\nexport function updateJoystickPosition(elements: UIElements, active: boolean, dx: number, dy: number, maxRadius: number): void {\n if (!elements.joystickThumb)\n return;\n if (active) {\n elements.joystickThumb.classList.add('active');\n // Clamp the offset to maxRadius\n const distance = Math.sqrt(dx * dx + dy * dy);\n const clampedDistance = Math.min(distance, maxRadius);\n const scale = distance > 0 ? clampedDistance / distance : 0;\n const clampedX = dx * scale;\n const clampedY = dy * scale;\n elements.joystickThumb.style.transform = `translate(${clampedX}px, ${clampedY}px)`;\n }\n else {\n elements.joystickThumb.classList.remove('active');\n elements.joystickThumb.style.transform = 'translate(0, 0)';\n }\n}\n/**\n * Update look zone visual state based on touch input\n */\nexport function updateLookZoneState(elements: UIElements, active: boolean): void {\n if (!elements.lookZone)\n return;\n if (active) {\n elements.lookZone.classList.add('active');\n }\n else {\n elements.lookZone.classList.remove('active');\n }\n}\n/**\n * Update active waypoint in the waypoint list dropdown\n */\nexport function updateWaypointListActive(elements: UIElements, index: number): void {\n if (!elements.waypointListContainer)\n return;\n const items = elements.waypointListContainer.querySelectorAll('.storysplat-waypoint-item');\n items.forEach((item, i) => {\n if (i === index) {\n item.classList.add('active');\n }\n else {\n item.classList.remove('active');\n }\n });\n}\n/**\n * Setup waypoint list click handlers\n */\nexport function setupWaypointListClickHandlers(elements: UIElements, onWaypointClick: (index: number) => void): void {\n if (!elements.waypointListContainer)\n return;\n const items = elements.waypointListContainer.querySelectorAll('.storysplat-waypoint-item');\n const toggleBtn = elements.waypointListContainer.querySelector('.storysplat-waypoint-list-toggle');\n const dropdown = elements.waypointListContainer.querySelector('.storysplat-waypoint-list-dropdown');\n items.forEach((item) => {\n item.addEventListener('click', () => {\n const index = parseInt(item.getAttribute('data-waypoint-index') || '0', 10);\n onWaypointClick(index);\n // Close dropdown after selection\n toggleBtn?.classList.remove('open');\n dropdown?.classList.remove('open');\n });\n });\n}\n/**\n * Lazy load UI options\n */\nexport interface LazyLoadUIOptions {\n thumbnailUrl?: string;\n /**\n * Type of media for the thumbnail.\n * - 'image': Static image (jpg, png, webp)\n * - 'video': Video file (mp4, webm) - will autoplay muted and loop\n * - 'gif': Animated GIF\n * If not specified, auto-detects from URL extension\n */\n thumbnailType?: 'image' | 'video' | 'gif';\n buttonText?: string;\n uiColor?: string;\n onStart: () => void;\n}\n/**\n * Detect media type from URL extension\n */\nfunction detectMediaType(url: string): 'image' | 'video' | 'gif' {\n const lowerUrl = url.toLowerCase();\n // Check for video extensions\n if (lowerUrl.includes('.mp4') || lowerUrl.includes('.webm') || lowerUrl.includes('.mov') || lowerUrl.includes('.ogg')) {\n return 'video';\n }\n // Check for GIF\n if (lowerUrl.includes('.gif')) {\n return 'gif';\n }\n // Default to image\n return 'image';\n}\n/**\n * Create lazy load UI with thumbnail and start button\n * Supports image, video, and GIF thumbnails\n * Returns the container element for later removal\n */\nexport function createLazyLoadUI(container: HTMLElement, options: LazyLoadUIOptions): HTMLElement {\n const { thumbnailUrl, thumbnailType, buttonText = 'Start Experience', uiColor = '#4CAF50', onStart } = options;\n // Inject styles if not already present\n injectStyles(uiColor);\n // Add container class\n container.classList.add('storysplat-viewer-container');\n // Create lazy load container\n const lazyLoadContainer = document.createElement('div');\n lazyLoadContainer.className = 'storysplat-lazy-load-container';\n // Determine media type (auto-detect if not specified)\n const mediaType = thumbnailType || (thumbnailUrl ? detectMediaType(thumbnailUrl) : 'image');\n // Build HTML content\n let html = '';\n // Thumbnail if provided - supports image, video, and GIF\n if (thumbnailUrl) {\n if (mediaType === 'video') {\n // Video thumbnail - autoplay, muted, loop for background video effect\n html += `<video class=\"storysplat-lazy-load-thumbnail-video\" src=\"${thumbnailUrl}\" autoplay muted loop playsinline webkit-playsinline></video>`;\n }\n else {\n // Image or GIF thumbnail (GIFs work naturally with img tags)\n html += `<img class=\"storysplat-lazy-load-thumbnail\" src=\"${thumbnailUrl}\" alt=\"Scene preview\" />`;\n }\n }\n // Overlay gradient\n html += `<div class=\"storysplat-lazy-load-overlay\"></div>`;\n // Content with start button\n html += `\n <div class=\"storysplat-lazy-load-content\">\n <button class=\"storysplat-lazy-load-start-btn\" style=\"background: ${uiColor}\">\n <svg viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\"/>\n </svg>\n ${buttonText}\n </button>\n </div>\n `;\n lazyLoadContainer.innerHTML = html;\n container.appendChild(lazyLoadContainer);\n // Add click handler to start button\n const startBtn = lazyLoadContainer.querySelector('.storysplat-lazy-load-start-btn');\n startBtn?.addEventListener('click', () => {\n // Stop video playback if present before removing\n const video = lazyLoadContainer.querySelector('video');\n if (video) {\n video.pause();\n video.src = ''; // Clear source to stop loading\n }\n // Fade out and remove\n lazyLoadContainer.style.transition = 'opacity 0.3s ease-out';\n lazyLoadContainer.style.opacity = '0';\n setTimeout(() => {\n lazyLoadContainer.remove();\n onStart();\n }, 300);\n });\n return lazyLoadContainer;\n}\n","/**\n * CameraControls - PlayCanvas Camera Controller\n *\n * Ported from PlayCanvas engine scripts/esm/camera-controls.mjs\n * Provides orbit, fly, and focus camera modes with mouse, touch, and gamepad support.\n *\n * Controls:\n * - LMB drag: Orbit around focus point\n * - RMB drag + WASD: Fly mode\n * - Shift + drag / MMB: Pan\n * - Scroll / Pinch: Zoom\n * - Double-click: Focus on point (handled externally)\n */\n\nimport * as pc from 'playcanvas';\n\ntype CameraComponentWithSystem = Omit<pc.CameraComponent, 'system'> & {\n system: {\n app?: pc.Application;\n };\n};\n\ntype FocusControllerWithComplete = Omit<pc.FocusController, 'complete'> & {\n complete?: () => boolean;\n};\n\ntype AppWithXR = Omit<pc.Application, 'xr'> & {\n xr?: {\n active?: boolean;\n };\n};\n\ninterface CollisionEntity extends pc.Entity {\n _collisionMeshType?: string;\n _collisionBounds?: pc.BoundingBox;\n}\n\nconst tmpV1 = new pc.Vec3();\nconst tmpV2 = new pc.Vec3();\nconst pose = new pc.Pose();\nconst frame = new pc.InputFrame({\n move: [0, 0, 0],\n rotate: [0, 0, 0]\n});\n\n/**\n * Calculate the damp rate for smooth interpolation\n */\nexport const damp = (damping: number, dt: number): number => 1 - Math.pow(damping, dt * 1000);\n\n/**\n * Apply dead zone to gamepad stick input\n */\nconst applyDeadZone = (stick: number[], low: number, high: number): void => {\n const mag = Math.sqrt(stick[0] * stick[0] + stick[1] * stick[1]);\n if (mag < low) {\n stick.fill(0);\n return;\n }\n const scale = (mag - low) / (high - low);\n stick[0] *= scale / mag;\n stick[1] *= scale / mag;\n};\n\n/**\n * Converts screen space mouse deltas to world space pan vector\n */\nconst screenToWorld = (\n camera: pc.CameraComponent,\n dx: number,\n dy: number,\n dz: number,\n out: pc.Vec3 = new pc.Vec3()\n): pc.Vec3 => {\n const { fov, aspectRatio, horizontalFov, projection, orthoHeight } = camera;\n const app = (camera as unknown as CameraComponentWithSystem).system?.app;\n const { width, height } = app?.graphicsDevice?.clientRect || { width: 1920, height: 1080 };\n\n // normalize deltas to device coord space\n out.set(\n -(dx / width) * 2,\n (dy / height) * 2,\n 0\n );\n\n // calculate half size of the view frustum at the current distance\n const halfSize = tmpV2.set(0, 0, 0);\n if (projection === pc.PROJECTION_PERSPECTIVE) {\n const halfSlice = dz * Math.tan(0.5 * fov * pc.math.DEG_TO_RAD);\n if (horizontalFov) {\n halfSize.set(halfSlice, halfSlice / aspectRatio, 0);\n } else {\n halfSize.set(halfSlice * aspectRatio, halfSlice, 0);\n }\n } else {\n halfSize.set(orthoHeight * aspectRatio, orthoHeight, 0);\n }\n\n // scale by device coord space\n out.mul(halfSize);\n return out;\n};\n\ntype CameraMode = 'orbit' | 'fly' | 'focus';\ntype MobileInputLayout = 'joystick-joystick' | 'joystick-touch' | 'touch-joystick' | 'touch-touch';\n\nexport interface CameraControlsConfig {\n enableOrbit?: boolean;\n enableFly?: boolean;\n enablePan?: boolean;\n focusPoint?: pc.Vec3;\n moveSpeed?: number;\n moveFastSpeed?: number;\n moveSlowSpeed?: number;\n rotateSpeed?: number;\n rotateTouchSens?: number;\n rotateJoystickSens?: number;\n zoomSpeed?: number;\n zoomPinchSens?: number;\n focusDamping?: number;\n rotateDamping?: number;\n moveDamping?: number;\n zoomDamping?: number;\n pitchRange?: pc.Vec2;\n yawRange?: pc.Vec2;\n zoomRange?: pc.Vec2;\n gamepadDeadZone?: pc.Vec2;\n mobileInputLayout?: MobileInputLayout;\n /** Invert camera rotation (Y-axis) for inverted mouse/touch controls */\n invertRotation?: boolean;\n}\n\n/**\n * CameraControls class - Manages camera with orbit, fly, and focus modes\n */\nexport class CameraControls {\n private camera: pc.Entity;\n private cameraComponent: pc.CameraComponent;\n private app: pc.Application;\n private enabled: boolean = true;\n\n // Mode state\n private _mode: CameraMode = 'orbit';\n private _enableOrbit: boolean = true;\n private _enableFly: boolean = true;\n enablePan: boolean = true;\n\n // Controllers\n private _flyController: pc.FlyController;\n private _orbitController: pc.OrbitController;\n private _focusController: pc.FocusController;\n private _controller: pc.InputController;\n private _pose: pc.Pose = new pc.Pose();\n private _preFocusMode: CameraMode = 'orbit';\n\n // Input sources\n private _desktopInput: pc.KeyboardMouseSource;\n private _orbitMobileInput: pc.MultiTouchSource;\n private _flyMobileInput: pc.DualGestureSource;\n private _gamepadInput: pc.GamepadSource;\n\n // Zoom\n private _startZoomDist: number = 0;\n // Limit pitch to ±89° to avoid gimbal lock (world flip at ±90°)\n private _pitchRange: pc.Vec2 = new pc.Vec2(-89, 89);\n private _yawRange: pc.Vec2 = new pc.Vec2(-Infinity, Infinity);\n\n // Store the last focus point for orbit mode\n private _lastFocusPoint: pc.Vec3 = new pc.Vec3(0, 0, 0);\n private _zoomRange: pc.Vec2 = new pc.Vec2(0.01, Infinity);\n\n // Debug: track last yaw for discontinuity detection\n private _lastYaw?: number;\n\n // State tracking\n private _state = {\n axis: new pc.Vec3(),\n shift: 0,\n ctrl: 0,\n mouse: [0, 0, 0],\n touches: 0\n };\n\n // Speed settings\n moveSpeed: number = 25;\n moveFastSpeed: number = 50;\n moveSlowSpeed: number = 10;\n rotateSpeed: number = 0.05;\n rotateTouchSens: number = 1.0; // Touch sensitivity (matches desktop)\n rotateJoystickSens: number = 1;\n zoomSpeed: number = 0.001;\n zoomPinchSens: number = 5;\n keyboardSpeedMultiplier: number = 1.5; // Desktop keyboard moves faster than mobile joystick\n gamepadDeadZone: pc.Vec2 = new pc.Vec2(0.3, 0.6);\n /** Invert camera rotation (Y-axis) */\n invertRotation: boolean = false;\n\n // Joystick event name for UI\n joystickEventName: string = 'joystick';\n\n // Collision detection for fly mode\n private _collisionEntities: pc.Entity[] = [];\n private _collisionRadius: number = 0.3;\n private _prevPosition: pc.Vec3 = new pc.Vec3();\n private _prevPositionValid: boolean = false;\n private _collisionTestVec: pc.Vec3 = new pc.Vec3();\n\n // Cleanup handlers\n private _destroyHandler: (() => void) | null = null;\n\n constructor(camera: pc.Entity, app: pc.Application, config: CameraControlsConfig = {}) {\n this.camera = camera;\n this.app = app;\n\n const cameraComponent = camera.camera;\n if (!cameraComponent) {\n throw new Error('CameraControls: camera component not found on entity');\n }\n this.cameraComponent = cameraComponent;\n\n // Initialize controllers\n this._flyController = new pc.FlyController();\n this._orbitController = new pc.OrbitController();\n this._focusController = new pc.FocusController();\n\n // Set default dampening values for responsive BabylonJS-like feel\n // Lower values = more responsive, less \"floaty\" (0.75 matches BabylonJS snappiness better than 0.9)\n this._flyController.moveDamping = 0.75;\n this._flyController.rotateDamping = 0.75;\n this._orbitController.rotateDamping = 0.75;\n this._orbitController.zoomDamping = 0.8;\n\n // Set orbit controller defaults\n this._orbitController.zoomRange = new pc.Vec2(0.01, Infinity);\n\n // Initialize input sources\n const canvas = app.graphicsDevice.canvas;\n this._desktopInput = new pc.KeyboardMouseSource();\n this._orbitMobileInput = new pc.MultiTouchSource();\n this._flyMobileInput = new pc.DualGestureSource();\n this._gamepadInput = new pc.GamepadSource();\n\n // Attach input sources to canvas\n this._desktopInput.attach(canvas);\n this._orbitMobileInput.attach(canvas);\n this._flyMobileInput.attach(canvas);\n this._gamepadInput.attach(canvas);\n\n // Expose UI joystick events\n this._flyMobileInput.on('joystick:position:left', ([bx, by, sx, sy]: number[]) => {\n // Always fire release events (bx < 0) to prevent stuck joystick UI\n if (bx < 0 || this._mode === 'fly') {\n this.app.fire(`${this.joystickEventName}:left`, bx, by, sx, sy);\n }\n });\n this._flyMobileInput.on('joystick:position:right', ([bx, by, sx, sy]: number[]) => {\n if (bx < 0 || this._mode === 'fly') {\n this.app.fire(`${this.joystickEventName}:right`, bx, by, sx, sy);\n }\n });\n\n // Initialize pose from camera position\n this._pose.look(this.camera.getPosition(), pc.Vec3.ZERO);\n\n // Set initial mode\n this._setMode('orbit');\n\n // Store controller reference\n this._controller = this._orbitController;\n\n // Apply config\n if (config.enableOrbit !== undefined) this.enableOrbit = config.enableOrbit;\n if (config.enableFly !== undefined) this.enableFly = config.enableFly;\n if (config.enablePan !== undefined) this.enablePan = config.enablePan;\n if (config.focusPoint) this.focusPoint = config.focusPoint;\n if (config.moveSpeed !== undefined) this.moveSpeed = config.moveSpeed;\n if (config.moveFastSpeed !== undefined) this.moveFastSpeed = config.moveFastSpeed;\n if (config.moveSlowSpeed !== undefined) this.moveSlowSpeed = config.moveSlowSpeed;\n if (config.rotateSpeed !== undefined) this.rotateSpeed = config.rotateSpeed;\n if (config.rotateTouchSens !== undefined) this.rotateTouchSens = config.rotateTouchSens;\n if (config.rotateJoystickSens !== undefined) this.rotateJoystickSens = config.rotateJoystickSens;\n if (config.zoomSpeed !== undefined) this.zoomSpeed = config.zoomSpeed;\n if (config.zoomPinchSens !== undefined) this.zoomPinchSens = config.zoomPinchSens;\n if (config.focusDamping !== undefined) this.focusDamping = config.focusDamping;\n if (config.rotateDamping !== undefined) this.rotateDamping = config.rotateDamping;\n if (config.moveDamping !== undefined) this.moveDamping = config.moveDamping;\n if (config.zoomDamping !== undefined) this.zoomDamping = config.zoomDamping;\n if (config.pitchRange) this.pitchRange = config.pitchRange;\n if (config.yawRange) this.yawRange = config.yawRange;\n if (config.zoomRange) this.zoomRange = config.zoomRange;\n if (config.gamepadDeadZone) this.gamepadDeadZone = config.gamepadDeadZone;\n if (config.mobileInputLayout) this.mobileInputLayout = config.mobileInputLayout;\n if (config.invertRotation !== undefined) this.invertRotation = config.invertRotation;\n }\n\n // Enable/disable getters and setters\n set enableFly(enable: boolean) {\n this._enableFly = enable;\n if (!this._enableFly && this._mode === 'fly') {\n this._setMode('orbit');\n }\n }\n\n get enableFly(): boolean {\n return this._enableFly;\n }\n\n set enableOrbit(enable: boolean) {\n this._enableOrbit = enable;\n if (!this._enableOrbit && this._mode === 'orbit') {\n this._setMode('fly');\n }\n }\n\n get enableOrbit(): boolean {\n return this._enableOrbit;\n }\n\n // Damping getters/setters\n set focusDamping(damping: number) {\n this._focusController.focusDamping = damping;\n }\n\n get focusDamping(): number {\n return this._focusController.focusDamping;\n }\n\n set moveDamping(damping: number) {\n this._flyController.moveDamping = damping;\n }\n\n get moveDamping(): number {\n return this._flyController.moveDamping;\n }\n\n set rotateDamping(damping: number) {\n this._flyController.rotateDamping = damping;\n this._orbitController.rotateDamping = damping;\n }\n\n get rotateDamping(): number {\n return this._orbitController.rotateDamping;\n }\n\n set zoomDamping(damping: number) {\n this._orbitController.zoomDamping = damping;\n }\n\n get zoomDamping(): number {\n return this._orbitController.zoomDamping;\n }\n\n // Focus point getter/setter\n set focusPoint(point: pc.Vec3) {\n const position = this.camera.getPosition();\n this._startZoomDist = position.distance(point);\n this._controller.attach(this._pose.look(position, point), false);\n }\n\n get focusPoint(): pc.Vec3 {\n return this._pose.getFocus(tmpV1);\n }\n\n // Range getters/setters\n set pitchRange(range: pc.Vec2) {\n this._pitchRange.copy(range);\n this._flyController.pitchRange = this._pitchRange;\n this._orbitController.pitchRange = this._pitchRange;\n }\n\n get pitchRange(): pc.Vec2 {\n return this._pitchRange;\n }\n\n set yawRange(range: pc.Vec2) {\n this._yawRange.x = pc.math.clamp(range.x, -360, 360);\n this._yawRange.y = pc.math.clamp(range.y, -360, 360);\n this._flyController.yawRange = this._yawRange;\n this._orbitController.yawRange = this._yawRange;\n }\n\n get yawRange(): pc.Vec2 {\n return this._yawRange;\n }\n\n set zoomRange(range: pc.Vec2) {\n this._zoomRange.x = range.x;\n this._zoomRange.y = range.y <= range.x ? Infinity : range.y;\n this._orbitController.zoomRange = this._zoomRange;\n }\n\n get zoomRange(): pc.Vec2 {\n return this._zoomRange;\n }\n\n // Mobile input layout\n set mobileInputLayout(layout: MobileInputLayout) {\n if (!/(?:joystick|touch)-(?:joystick|touch)/.test(layout)) {\n console.warn(`CameraControls: invalid mobile input layout: ${layout}`);\n return;\n }\n this._flyMobileInput.layout = layout;\n }\n\n get mobileInputLayout(): MobileInputLayout {\n return this._flyMobileInput.layout as MobileInputLayout;\n }\n\n /**\n * Get current camera mode\n */\n get mode(): CameraMode {\n return this._mode;\n }\n\n /**\n * Set camera mode\n */\n private _setMode(mode: CameraMode): void {\n // Override mode depending on enabled features\n if (this._enableFly && !this._enableOrbit) {\n mode = 'fly';\n } else if (!this._enableFly && this._enableOrbit) {\n mode = 'orbit';\n } else if (!this._enableFly && !this._enableOrbit) {\n console.warn('CameraControls: both fly and orbit modes are disabled');\n return;\n }\n\n const previousMode = this._mode;\n if (previousMode === mode) return;\n this._mode = mode;\n\n // Detach old controller\n if (this._controller) {\n this._controller.detach();\n }\n\n // Attach new controller\n switch (this._mode) {\n case 'orbit':\n this._controller = this._orbitController;\n // When switching to orbit (especially after focus), set up orbit around the last focus point\n if (previousMode === 'focus') {\n const position = this.camera.getPosition();\n this._pose.look(position, this._lastFocusPoint);\n }\n // Reset yaw tracking to prevent false discontinuity detection on mode switch\n this._lastYaw = this._pose.angles.y;\n break;\n case 'fly':\n this._controller = this._flyController;\n // When returning to fly after focus, sync pose from current camera position\n if (previousMode === 'focus') {\n const position = this.camera.getPosition();\n this._pose.look(position, this._lastFocusPoint);\n }\n break;\n case 'focus':\n this._controller = this._focusController;\n break;\n }\n this._controller.attach(this._pose, false);\n\n // Emit mode change event for UI updates\n this.app.fire('cameracontrols:modechange', this._mode);\n }\n\n /**\n * Public method to set camera mode (orbit or fly only)\n * Used by UI toggle buttons to switch between orbit and fly modes\n */\n setMode(mode: 'orbit' | 'fly'): void {\n this._setMode(mode);\n }\n\n /**\n * Focus camera on a point with smooth animation.\n * After the focus animation completes, orbit mode will use this point as the orbit center.\n */\n focus(focusPoint: pc.Vec3, resetZoom: boolean = false): void {\n // Store the focus point so orbit mode uses it as the center\n this._lastFocusPoint.copy(focusPoint);\n\n // Remember current mode to restore after focus completes\n if (this._mode !== 'focus') {\n this._preFocusMode = this._mode;\n }\n this._setMode('focus');\n const zoomDist = resetZoom\n ? this._startZoomDist\n : this.camera.getPosition().distance(focusPoint);\n this._startZoomDist = zoomDist; // Update the zoom distance for orbit mode\n const position = tmpV1.copy(this.camera.forward).mulScalar(-zoomDist).add(focusPoint);\n this._controller.attach(pose.look(position, focusPoint));\n }\n\n /**\n * Look at a point without moving\n */\n look(focusPoint: pc.Vec3, resetZoom: boolean = false): void {\n if (this._mode !== 'focus') {\n this._preFocusMode = this._mode;\n }\n this._setMode('focus');\n const position = resetZoom\n ? tmpV1.copy(this.camera.getPosition()).sub(focusPoint).normalize().mulScalar(this._startZoomDist).add(focusPoint)\n : this.camera.getPosition();\n this._controller.attach(pose.look(position, focusPoint));\n }\n\n /**\n * Reset camera to a position looking at focus point\n */\n reset(focusPoint: pc.Vec3, position: pc.Vec3): void {\n if (this._mode !== 'focus') {\n this._preFocusMode = this._mode;\n }\n this._setMode('focus');\n this._controller.attach(pose.look(position, focusPoint));\n }\n\n /**\n * Sync internal pose from camera's current position and rotation.\n * If a target is provided, the pose is set up to look at that target from the current position.\n * Otherwise, preserves the camera's exact rotation.\n */\n syncFromCamera(target?: pc.Vec3): void {\n const position = this.camera.getPosition().clone();\n\n // Reset collision tracking so the first fly frame after sync just records position\n this._prevPositionValid = false;\n\n if (target) {\n // When we have a target, use pose.look() to properly set up the pose\n // This calculates correct angles to look at the target from current position\n this._lastFocusPoint.copy(target);\n const focusDistance = position.distance(target);\n this._startZoomDist = focusDistance;\n this._pose.distance = focusDistance;\n\n // Use pose.look() which properly calculates yaw/pitch to face the target\n this._pose.look(position, target);\n\n // Normalize yaw to [-180, 180] range\n let yaw = this._pose.angles.y;\n while (yaw > 180) yaw -= 360;\n while (yaw < -180) yaw += 360;\n this._pose.angles.y = yaw;\n this._lastYaw = yaw;\n\n // Clamp pitch to prevent flipping (avoid gimbal lock near ±90°)\n this._pose.angles.x = Math.max(-89, Math.min(89, this._pose.angles.x));\n\n // Ensure no roll\n this._pose.angles.z = 0;\n } else {\n // No target - preserve camera's exact rotation\n const eulerAngles = this.camera.getEulerAngles();\n\n // Set pose position directly from camera\n this._pose.position.copy(position);\n\n // Set pose angles directly from camera's euler angles\n this._pose.angles.x = eulerAngles.x;\n this._pose.angles.y = eulerAngles.y;\n this._pose.angles.z = 0; // Always zero roll to prevent flipping\n\n // Normalize yaw to [-180, 180] range\n let yaw = this._pose.angles.y;\n while (yaw > 180) yaw -= 360;\n while (yaw < -180) yaw += 360;\n this._pose.angles.y = yaw;\n this._lastYaw = yaw;\n\n // Clamp pitch to prevent flipping\n this._pose.angles.x = Math.max(-89, Math.min(89, this._pose.angles.x));\n\n // Calculate a focus point 10 units in front of the camera\n const forward = this.camera.forward.clone();\n this._lastFocusPoint.copy(position).add(forward.mulScalar(10));\n\n const focusDistance = 10;\n this._startZoomDist = focusDistance;\n this._pose.distance = focusDistance;\n }\n\n // Reattach the current controller with the synced pose\n this._controller.attach(this._pose, false);\n }\n\n /**\n * Sync internal pose from explicit position and rotation values.\n * Use this when you need to bypass the camera entity's current state.\n * Also sets the camera entity to match these values.\n */\n syncFromPose(position: pc.Vec3, rotation: pc.Quat, focusTarget?: pc.Vec3): void {\n // Reset collision tracking so the first fly frame after sync just records position\n this._prevPositionValid = false;\n\n // Set the camera entity to match the provided pose\n this.camera.setPosition(position);\n this.camera.setRotation(rotation);\n\n // Get euler angles from the quaternion\n const tempEntity = new pc.Entity();\n tempEntity.setRotation(rotation);\n const eulerAngles = tempEntity.getEulerAngles();\n\n // Set pose position\n this._pose.position.copy(position);\n\n // Set pose angles from the rotation\n this._pose.angles.x = eulerAngles.x;\n this._pose.angles.y = eulerAngles.y;\n this._pose.angles.z = 0; // Always zero roll\n\n // Normalize yaw to [-180, 180] range\n let yaw = this._pose.angles.y;\n while (yaw > 180) yaw -= 360;\n while (yaw < -180) yaw += 360;\n this._pose.angles.y = yaw;\n this._lastYaw = yaw;\n\n // Clamp pitch to prevent flipping\n this._pose.angles.x = Math.max(-89, Math.min(89, this._pose.angles.x));\n\n // Calculate focus point\n if (focusTarget) {\n this._lastFocusPoint.copy(focusTarget);\n } else {\n // Calculate a focus point 10 units in front\n const forward = this.camera.forward.clone();\n this._lastFocusPoint.copy(position).add(forward.mulScalar(10));\n }\n\n const focusDistance = position.distance(this._lastFocusPoint);\n this._startZoomDist = focusDistance;\n this._pose.distance = focusDistance;\n\n // Reattach the current controller with the synced pose\n this._controller.attach(this._pose, false);\n }\n\n /**\n * Enable controls\n */\n enable(): void {\n this.enabled = true;\n }\n\n /**\n * Disable controls\n */\n disable(): void {\n this.enabled = false;\n // Discard any pending inputs\n this._desktopInput.read();\n this._orbitMobileInput.read();\n this._flyMobileInput.read();\n this._gamepadInput.read();\n }\n\n /**\n * Update camera controls - call this every frame\n */\n update(dt: number): void {\n if (!this.enabled) return;\n\n const { keyCode } = pc.KeyboardMouseSource;\n const { key, button, mouse, wheel } = this._desktopInput.read();\n const { touch, pinch, count } = this._orbitMobileInput.read();\n const { leftInput, rightInput } = this._flyMobileInput.read();\n const { leftStick, rightStick } = this._gamepadInput.read();\n\n // Apply dead zone to gamepad sticks\n applyDeadZone(leftStick, this.gamepadDeadZone.x, this.gamepadDeadZone.y);\n applyDeadZone(rightStick, this.gamepadDeadZone.x, this.gamepadDeadZone.y);\n\n // Update state\n this._state.axis.add(tmpV1.set(\n (key[keyCode.D] - key[keyCode.A]) + (key[keyCode.RIGHT] - key[keyCode.LEFT]),\n (key[keyCode.E] - key[keyCode.Q]),\n (key[keyCode.W] - key[keyCode.S]) + (key[keyCode.UP] - key[keyCode.DOWN])\n ));\n for (let i = 0; i < this._state.mouse.length; i++) {\n this._state.mouse[i] += button[i];\n }\n this._state.shift += key[keyCode.SHIFT];\n this._state.ctrl += key[keyCode.CTRL];\n this._state.touches += count[0];\n\n // No automatic mode switching - user controls mode via UI buttons only\n\n const orbit = +(this._mode === 'orbit');\n const fly = +(this._mode === 'fly');\n const double = +(this._state.touches > 1);\n const desktopPan = +(this._state.shift || this._state.mouse[1]);\n const mobileJoystick = +(this._flyMobileInput.layout.endsWith('joystick'));\n\n // Multipliers\n const moveMult = (this._state.shift ? this.moveFastSpeed : this._state.ctrl\n ? this.moveSlowSpeed : this.moveSpeed) * dt;\n const zoomMult = this.zoomSpeed * 60 * dt;\n const zoomTouchMult = zoomMult * this.zoomPinchSens;\n const rotateMult = this.rotateSpeed * 60 * dt;\n const rotateTouchMult = rotateMult * this.rotateTouchSens;\n const rotateJoystickMult = this.rotateSpeed * this.rotateJoystickSens * 60 * dt;\n\n const { deltas } = frame;\n\n // Desktop move\n const v = tmpV1.set(0, 0, 0);\n const keyMove = this._state.axis.clone().normalize();\n v.add(keyMove.mulScalar(fly * moveMult * this.keyboardSpeedMultiplier));\n const panMove = screenToWorld(this.cameraComponent, mouse[0], mouse[1], this._pose.distance);\n v.add(panMove.mulScalar(orbit * desktopPan * +this.enablePan));\n const wheelMove = tmpV2.set(0, 0, wheel[0]);\n v.add(wheelMove.mulScalar(orbit * zoomMult));\n deltas.move.append([v.x, v.y, v.z]);\n\n // Desktop rotate\n v.set(0, 0, 0);\n const mouseRotate = tmpV2.set(mouse[0], mouse[1], 0);\n v.add(mouseRotate.mulScalar((1 - (orbit * desktopPan)) * rotateMult));\n // Apply inversion if enabled (inverts Y/pitch axis)\n if (this.invertRotation) v.y = -v.y;\n deltas.rotate.append([v.x, v.y, v.z]);\n\n // Mobile move\n v.set(0, 0, 0);\n const flyMove = tmpV2.set(leftInput[0], 0, -leftInput[1]);\n v.add(flyMove.mulScalar(fly * moveMult));\n const orbitMove = screenToWorld(this.cameraComponent, touch[0], touch[1], this._pose.distance);\n v.add(orbitMove.mulScalar(orbit * double * +this.enablePan));\n const pinchMove = tmpV2.set(0, 0, pinch[0]);\n v.add(pinchMove.mulScalar(orbit * double * zoomTouchMult));\n deltas.move.append([v.x, v.y, v.z]);\n\n // Mobile rotate (uses rotateTouchMult for reduced sensitivity on touch devices)\n v.set(0, 0, 0);\n const orbitRotate = tmpV2.set(touch[0], touch[1], 0);\n v.add(orbitRotate.mulScalar(orbit * (1 - double) * rotateTouchMult));\n const flyRotate = tmpV2.set(rightInput[0], rightInput[1], 0);\n v.add(flyRotate.mulScalar(fly * (mobileJoystick ? rotateJoystickMult : rotateTouchMult)));\n // Apply inversion if enabled (inverts Y/pitch axis)\n if (this.invertRotation) v.y = -v.y;\n deltas.rotate.append([v.x, v.y, v.z]);\n\n // Gamepad move\n v.set(0, 0, 0);\n const stickMove = tmpV2.set(leftStick[0], 0, -leftStick[1]);\n v.add(stickMove.mulScalar(fly * moveMult));\n deltas.move.append([v.x, v.y, v.z]);\n\n // Gamepad rotate\n v.set(0, 0, 0);\n const stickRotate = tmpV2.set(rightStick[0], rightStick[1], 0);\n v.add(stickRotate.mulScalar(fly * rotateJoystickMult));\n // Apply inversion if enabled (inverts Y/pitch axis)\n if (this.invertRotation) v.y = -v.y;\n deltas.rotate.append([v.x, v.y, v.z]);\n\n // Check if XR is active - discard frame if so\n if ((this.app as AppWithXR).xr?.active) {\n frame.read();\n return;\n }\n\n // Check focus end - return to previous mode (orbit or fly)\n if (this._mode === 'focus') {\n const focusInterrupt = deltas.move.length() + deltas.rotate.length() > 0;\n const focusComplete = (this._focusController as FocusControllerWithComplete).complete?.() ?? false;\n if (focusInterrupt || focusComplete) {\n this._setMode(this._preFocusMode);\n }\n }\n\n // Update controller by consuming frame\n this._pose.copy(this._controller.update(frame, dt));\n\n // Collision detection for fly and orbit modes - prevents camera from moving through collision meshes\n // Uses axis-separated checks for wall-sliding behavior (camera slides along surfaces instead of stopping)\n // In fly mode: prevents WASD movement through walls\n // In orbit mode: prevents zoom/pan from pushing camera through walls\n if ((this._mode === 'fly' || this._mode === 'orbit') && this._collisionEntities.length > 0) {\n if (this._prevPositionValid) {\n const newPos = this._pose.position;\n const prev = this._prevPosition;\n const test = this._collisionTestVec;\n let corrected = false;\n\n // Test X axis independently\n test.set(newPos.x, prev.y, prev.z);\n if (this.checkCollision(test)) {\n newPos.x = prev.x;\n corrected = true;\n }\n\n // Test Y axis independently\n test.set(newPos.x, newPos.y, prev.z);\n if (this.checkCollision(test)) {\n newPos.y = prev.y;\n corrected = true;\n }\n\n // Test Z axis independently\n test.set(newPos.x, newPos.y, newPos.z);\n if (this.checkCollision(test)) {\n newPos.z = prev.z;\n corrected = true;\n }\n\n // Re-sync controller to corrected position to prevent internal state drift\n if (corrected) {\n this._controller.attach(this._pose, false);\n }\n }\n this._prevPosition.copy(this._pose.position);\n this._prevPositionValid = true;\n } else if (this._mode !== 'fly' && this._mode !== 'orbit') {\n // Reset tracking when not in fly/orbit mode so first frame initializes correctly\n this._prevPositionValid = false;\n }\n\n // Fix yaw discontinuity in orbit mode\n // PlayCanvas's Pose.lerp() uses `% 360` after lerpAngle which doesn't handle\n // negative angles correctly (e.g., -185 % 360 = -185, not 175)\n // This causes sudden jumps when yaw crosses the ±180° boundary\n if (this._mode === 'orbit') {\n // Normalize yaw to [-180, 180) range to prevent accumulation issues\n let yaw = this._pose.angles.y;\n while (yaw > 180) yaw -= 360;\n while (yaw < -180) yaw += 360;\n\n if (this._lastYaw !== undefined) {\n // Check for discontinuous jump (more than 90° in a single frame is unnatural)\n const yawDelta = yaw - this._lastYaw;\n\n // If there's a large jump, adjust to maintain continuity\n if (yawDelta > 180) {\n yaw -= 360;\n } else if (yawDelta < -180) {\n yaw += 360;\n }\n }\n\n this._pose.angles.y = yaw;\n this._lastYaw = yaw;\n }\n\n this.camera.setPosition(this._pose.position);\n this.camera.setEulerAngles(this._pose.angles);\n }\n\n /**\n * Set collision mesh entities for explore mode collision detection.\n * Prevents the camera from passing through collision meshes in fly and orbit modes.\n */\n setCollisionEntities(entities: pc.Entity[], radius?: number): void {\n this._collisionEntities = entities;\n if (radius !== undefined) this._collisionRadius = radius;\n this._prevPositionValid = false;\n }\n\n /**\n * Check if a position collides with any collision mesh (AABB check).\n * Uses the same approach as CharacterController for consistency.\n */\n private checkCollision(position: pc.Vec3): boolean {\n const r = this._collisionRadius;\n\n for (const entity of this._collisionEntities) {\n const entityPos = entity.getPosition();\n const entityScale = entity.getLocalScale();\n const meshType = (entity as CollisionEntity)._collisionMeshType;\n\n let halfWidth: number, halfHeight: number, halfDepth: number;\n let centerX: number, centerY: number, centerZ: number;\n\n // Check for custom bounds (imported GLB/GLTF meshes)\n const customBounds = (entity as CollisionEntity)._collisionBounds;\n if (customBounds) {\n // World-space AABB - use bounds center (mesh may not be at entity origin) and don't multiply by scale\n centerX = customBounds.center.x;\n centerY = customBounds.center.y;\n centerZ = customBounds.center.z;\n halfWidth = customBounds.halfExtents.x;\n halfHeight = customBounds.halfExtents.y;\n halfDepth = customBounds.halfExtents.z;\n } else if (meshType === 'floor' || meshType === 'plane') {\n // Floors/planes are flat - use scale for width/depth, thin height\n centerX = entityPos.x;\n centerY = entityPos.y;\n centerZ = entityPos.z;\n halfWidth = entityScale.x / 2;\n halfHeight = 0.05;\n halfDepth = entityScale.z / 2;\n } else {\n // Primitive shapes (cube, sphere treated as AABB)\n centerX = entityPos.x;\n centerY = entityPos.y;\n centerZ = entityPos.z;\n halfWidth = entityScale.x / 2;\n halfHeight = entityScale.y / 2;\n halfDepth = entityScale.z / 2;\n }\n\n const dx = Math.abs(position.x - centerX);\n const dy = Math.abs(position.y - centerY);\n const dz = Math.abs(position.z - centerZ);\n\n if (dx < halfWidth + r && dy < halfHeight + r && dz < halfDepth + r) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Clean up resources\n */\n destroy(): void {\n this._desktopInput.destroy();\n this._orbitMobileInput.destroy();\n this._flyMobileInput.destroy();\n this._gamepadInput.destroy();\n\n this._flyController.destroy();\n this._orbitController.destroy();\n }\n}\n","/**\n * CharacterController - First Person Character Controller with Collisions\n *\n * A lightweight character controller for PlayCanvas that provides:\n * - WASD/Arrow key movement\n * - Mouse look\n * - Gravity and ground detection via raycasting\n * - Collision detection with scene meshes via raycasting\n *\n * Does NOT require ammo.js physics - uses simple raycasting for collisions.\n */\nimport * as pc from 'playcanvas';\n/**\n * Calculate the damp rate for smooth interpolation\n * Same formula as PlayCanvas's camera-controls for consistent feel\n */\nconst damp = (damping: number, dt: number): number => 1 - Math.pow(damping, dt * 1000);\nexport interface CharacterControllerConfig {\n /** Movement speed in units per second */\n moveSpeed?: number;\n /** Sprint speed multiplier */\n sprintMultiplier?: number;\n /** Mouse look sensitivity */\n lookSensitivity?: number;\n /** Player height (camera Y offset from ground) */\n playerHeight?: number;\n /** Gravity strength (units per second squared) */\n gravity?: number;\n /** Maximum fall speed */\n maxFallSpeed?: number;\n /** Jump velocity */\n jumpVelocity?: number;\n /** Collision radius for horizontal movement */\n collisionRadius?: number;\n /** Step height for climbing small obstacles */\n stepHeight?: number;\n /** Ground check distance below feet */\n groundCheckDistance?: number;\n /** Movement dampening for smooth start/stop (0-1, higher = more smoothing) */\n moveDamping?: number;\n}\ninterface CollisionMeshData {\n entity?: pc.Entity;\n meshType: string;\n position?: number[] | { x: number; y: number; z: number };\n rotation?: number[] | { x: number; y: number; z: number };\n scaling?: number[] | { x: number; y: number; z: number };\n customMeshUrl?: string;\n}\n/** Extract xyz from number[] or {x,y,z} format */\nfunction toXYZ(v: number[] | { x: number; y: number; z: number } | undefined, fallback: [number, number, number]): [number, number, number] {\n if (!v) return fallback;\n if (Array.isArray(v)) return [v[0] ?? fallback[0], v[1] ?? fallback[1], v[2] ?? fallback[2]];\n return [v.x ?? fallback[0], v.y ?? fallback[1], v.z ?? fallback[2]];\n}\ninterface CollisionEntity extends pc.Entity {\n _collisionBounds?: pc.BoundingBox;\n _collisionMeshType?: string;\n}\n/**\n * CharacterController class - First person controller with collision detection\n */\nexport class CharacterController {\n private camera: pc.Entity;\n private app: pc.Application;\n private enabled: boolean = false;\n // Movement state\n private velocity: pc.Vec3 = new pc.Vec3();\n private isGrounded: boolean = false;\n private yaw: number = 0;\n private pitch: number = 0;\n // Input state\n private keys: {\n [key: string]: boolean;\n } = {};\n private mouseLocked: boolean = false;\n // Configuration\n moveSpeed: number = 8; // Increased for faster movement\n sprintMultiplier: number = 2;\n lookSensitivity: number = 0.002;\n playerHeight: number = 1.6;\n gravity: number = 20;\n maxFallSpeed: number = 50;\n jumpVelocity: number = 8;\n collisionRadius: number = 0.3;\n stepHeight: number = 0.3;\n groundCheckDistance: number = 0.1;\n moveDamping: number = 0.9; // Smooth movement like BabylonJS\n // Velocity smoothing for gradual start/stop\n private horizontalVelocity: pc.Vec3 = new pc.Vec3();\n private targetVelocity: pc.Vec3 = new pc.Vec3();\n // Collision meshes\n private collisionEntities: pc.Entity[] = [];\n private floorEntity: pc.Entity | null = null;\n // Event handlers (for cleanup)\n private keydownHandler: ((e: KeyboardEvent) => void) | null = null;\n private keyupHandler: ((e: KeyboardEvent) => void) | null = null;\n private mousemoveHandler: ((e: MouseEvent) => void) | null = null;\n private clickHandler: ((e: MouseEvent) => void) | null = null;\n private pointerlockchangeHandler: (() => void) | null = null;\n // Temporary vectors for calculations\n private tmpVec = new pc.Vec3();\n private tmpVec2 = new pc.Vec3();\n private forward = new pc.Vec3();\n private right = new pc.Vec3();\n constructor(camera: pc.Entity, app: pc.Application, config: CharacterControllerConfig = {}) {\n this.camera = camera;\n this.app = app;\n // Apply config\n if (config.moveSpeed !== undefined)\n this.moveSpeed = config.moveSpeed;\n if (config.sprintMultiplier !== undefined)\n this.sprintMultiplier = config.sprintMultiplier;\n if (config.lookSensitivity !== undefined)\n this.lookSensitivity = config.lookSensitivity;\n if (config.playerHeight !== undefined)\n this.playerHeight = config.playerHeight;\n if (config.gravity !== undefined)\n this.gravity = config.gravity;\n if (config.maxFallSpeed !== undefined)\n this.maxFallSpeed = config.maxFallSpeed;\n if (config.jumpVelocity !== undefined)\n this.jumpVelocity = config.jumpVelocity;\n if (config.collisionRadius !== undefined)\n this.collisionRadius = config.collisionRadius;\n if (config.stepHeight !== undefined)\n this.stepHeight = config.stepHeight;\n if (config.groundCheckDistance !== undefined)\n this.groundCheckDistance = config.groundCheckDistance;\n if (config.moveDamping !== undefined)\n this.moveDamping = config.moveDamping;\n // Initialize yaw/pitch from camera rotation\n const angles = this.camera.getEulerAngles();\n this.pitch = angles.x;\n this.yaw = angles.y;\n }\n /**\n * Create collision meshes from scene data\n */\n async createCollisionMeshes(collisionMeshesData: CollisionMeshData[]): Promise<void> {\n if (!collisionMeshesData || collisionMeshesData.length === 0)\n return;\n console.log('[CharacterController] Creating collision meshes:', collisionMeshesData.length);\n const loadPromises: Promise<void>[] = [];\n collisionMeshesData.forEach((data, index) => {\n // Handle custom mesh (GLB/GLTF files)\n if (data.meshType === 'custom' && data.customMeshUrl) {\n const promise = this.loadCustomCollisionMesh(data, index);\n loadPromises.push(promise);\n return;\n }\n let entity: pc.Entity | null = null;\n switch (data.meshType) {\n case 'cube':\n entity = new pc.Entity(`collision-cube-${index}`);\n entity.addComponent('render', {\n type: 'box'\n });\n break;\n case 'sphere':\n entity = new pc.Entity(`collision-sphere-${index}`);\n entity.addComponent('render', {\n type: 'sphere'\n });\n break;\n case 'floor':\n entity = new pc.Entity(`collision-floor-${index}`);\n entity.addComponent('render', {\n type: 'plane'\n });\n this.floorEntity = entity;\n break;\n case 'plane':\n default:\n entity = new pc.Entity(`collision-plane-${index}`);\n entity.addComponent('render', {\n type: 'plane'\n });\n break;\n }\n if (entity) {\n this.configureCollisionEntity(entity, data);\n }\n });\n // Wait for all custom meshes to load\n if (loadPromises.length > 0) {\n await Promise.all(loadPromises);\n }\n console.log('[CharacterController] Created', this.collisionEntities.length, 'collision entities');\n }\n /**\n * Load a custom collision mesh (GLB/GLTF)\n */\n private async loadCustomCollisionMesh(data: CollisionMeshData, index: number): Promise<void> {\n const url = data.customMeshUrl;\n if (!url) return;\n console.log('[CharacterController] Loading custom collision mesh:', url);\n try {\n // Determine asset type from URL\n const ext = url.split('?')[0].split('.').pop()?.toLowerCase() || 'glb';\n const assetType = (ext === 'gltf' || ext === 'glb') ? 'container' : 'model';\n const asset = new pc.Asset(`collision-custom-${index}`, assetType, { url });\n await new Promise<void>((resolve, reject) => {\n asset.ready(() => {\n try {\n const entity = new pc.Entity(`collision-custom-${index}`);\n if (assetType === 'container') {\n // GLB/GLTF container - instantiate the model\n const containerResource = asset.resource as pc.ContainerResource;\n if (containerResource && containerResource.instantiateRenderEntity) {\n const renderEntity = containerResource.instantiateRenderEntity();\n // Re-parent all children to our collision entity\n while (renderEntity.children.length > 0) {\n entity.addChild(renderEntity.children[0]);\n }\n renderEntity.destroy();\n }\n }\n else {\n // Regular model\n entity.addComponent('model', {\n asset: asset\n });\n }\n this.configureCollisionEntity(entity, data);\n // Store bounding box for custom mesh collision\n this.computeAndStoreBounds(entity);\n resolve();\n }\n catch (err) {\n console.error('[CharacterController] Error setting up custom mesh:', err);\n reject(err);\n }\n });\n asset.on('error', (err: unknown) => {\n console.error('[CharacterController] Error loading custom mesh:', url, err);\n reject(err);\n });\n this.app.assets.add(asset);\n this.app.assets.load(asset);\n });\n }\n catch (error) {\n console.error('[CharacterController] Failed to load custom collision mesh:', url, error);\n }\n }\n /**\n * Compute and store bounding box for a collision entity\n */\n private computeAndStoreBounds(entity: pc.Entity): void {\n // Traverse entity and its children to compute combined bounds\n const bounds = new pc.BoundingBox();\n let boundsInitialized = false;\n const traverse = (node: pc.Entity) => {\n if (node.render && node.render.meshInstances) {\n for (const mi of node.render.meshInstances) {\n if (mi.aabb) {\n if (!boundsInitialized) {\n bounds.copy(mi.aabb);\n boundsInitialized = true;\n }\n else {\n bounds.add(mi.aabb);\n }\n }\n }\n }\n for (const child of node.children) {\n if (child instanceof pc.Entity) {\n traverse(child);\n }\n }\n };\n traverse(entity);\n if (boundsInitialized) {\n (entity as CollisionEntity)._collisionBounds = bounds;\n }\n }\n /**\n * Configure a collision entity with transform and visibility\n */\n private configureCollisionEntity(entity: pc.Entity, data: CollisionMeshData): void {\n // Apply transform - convert from BabylonJS coordinates\n const pos = toXYZ(data.position, [0, 0, 0]);\n entity.setPosition(pos[0], pos[1], -pos[2]); // Negate Z\n const rot = toXYZ(data.rotation, [0, 0, 0]);\n entity.setEulerAngles(rot[0] * (180 / Math.PI), rot[1] * (180 / Math.PI), -rot[2] * (180 / Math.PI));\n const scale = toXYZ(data.scaling, [1, 1, 1]);\n entity.setLocalScale(scale[0], scale[1], scale[2]);\n // Make invisible but keep for collision\n this.setEntityVisibility(entity, false);\n // Store mesh type for collision logic\n (entity as CollisionEntity)._collisionMeshType = data.meshType;\n this.app.root.addChild(entity);\n this.collisionEntities.push(entity);\n }\n /**\n * Set visibility of entity and all children\n */\n private setEntityVisibility(entity: pc.Entity, visible: boolean): void {\n if (entity.render) {\n entity.render.enabled = visible;\n }\n for (const child of entity.children) {\n if (child instanceof pc.Entity) {\n this.setEntityVisibility(child, visible);\n }\n }\n }\n /**\n * Enable the character controller\n */\n enable(): void {\n if (this.enabled)\n return;\n this.enabled = true;\n // Sync yaw/pitch from current camera rotation\n const angles = this.camera.getEulerAngles();\n this.pitch = angles.x;\n this.yaw = angles.y;\n // Reset velocities\n this.velocity.set(0, 0, 0);\n this.horizontalVelocity.set(0, 0, 0);\n this.targetVelocity.set(0, 0, 0);\n // Setup input handlers\n this.setupInputHandlers();\n console.log('[CharacterController] Enabled');\n }\n /**\n * Disable the character controller\n */\n disable(): void {\n if (!this.enabled)\n return;\n this.enabled = false;\n // Remove input handlers\n this.removeInputHandlers();\n // Exit pointer lock\n if (document.pointerLockElement) {\n document.exitPointerLock();\n }\n console.log('[CharacterController] Disabled');\n }\n /**\n * Setup keyboard and mouse input handlers\n */\n private setupInputHandlers(): void {\n const canvas = this.app.graphicsDevice.canvas as HTMLCanvasElement;\n // Keyboard handlers\n this.keydownHandler = (e: KeyboardEvent) => {\n this.keys[e.code] = true;\n // Jump on space\n if (e.code === 'Space' && this.isGrounded) {\n this.velocity.y = this.jumpVelocity;\n this.isGrounded = false;\n }\n };\n this.keyupHandler = (e: KeyboardEvent) => {\n this.keys[e.code] = false;\n };\n // Mouse look handler\n this.mousemoveHandler = (e: MouseEvent) => {\n if (!this.mouseLocked)\n return;\n this.yaw -= e.movementX * this.lookSensitivity * 100;\n this.pitch -= e.movementY * this.lookSensitivity * 100;\n // Clamp pitch\n this.pitch = Math.max(-89, Math.min(89, this.pitch));\n };\n // Click to lock pointer\n this.clickHandler = () => {\n if (!this.mouseLocked) {\n canvas.requestPointerLock();\n }\n };\n // Pointer lock change handler\n this.pointerlockchangeHandler = () => {\n this.mouseLocked = document.pointerLockElement === canvas;\n };\n // Add event listeners\n document.addEventListener('keydown', this.keydownHandler);\n document.addEventListener('keyup', this.keyupHandler);\n document.addEventListener('mousemove', this.mousemoveHandler);\n canvas.addEventListener('click', this.clickHandler);\n document.addEventListener('pointerlockchange', this.pointerlockchangeHandler);\n }\n /**\n * Remove input handlers\n */\n private removeInputHandlers(): void {\n const canvas = this.app.graphicsDevice.canvas as HTMLCanvasElement;\n if (this.keydownHandler) {\n document.removeEventListener('keydown', this.keydownHandler);\n }\n if (this.keyupHandler) {\n document.removeEventListener('keyup', this.keyupHandler);\n }\n if (this.mousemoveHandler) {\n document.removeEventListener('mousemove', this.mousemoveHandler);\n }\n if (this.clickHandler) {\n canvas.removeEventListener('click', this.clickHandler);\n }\n if (this.pointerlockchangeHandler) {\n document.removeEventListener('pointerlockchange', this.pointerlockchangeHandler);\n }\n this.keys = {};\n }\n /**\n * Check if position collides with any collision mesh\n */\n private checkCollision(position: pc.Vec3, radius: number): boolean {\n // AABB collision check against collision entities\n for (const entity of this.collisionEntities) {\n const entityPos = entity.getPosition();\n const entityScale = entity.getLocalScale();\n const meshType = (entity as CollisionEntity)._collisionMeshType;\n if (meshType === 'floor' || meshType === 'plane') {\n // Skip floor for horizontal collision\n continue;\n }\n let halfWidth: number, halfHeight: number, halfDepth: number;\n let centerX: number, centerY: number, centerZ: number;\n // Check if entity has custom bounds (for imported GLB/GLTF meshes)\n const customBounds = (entity as CollisionEntity)._collisionBounds;\n if (customBounds) {\n // Custom bounds are stored as world-space AABB (mi.aabb already includes entity transform)\n // Use bounds center (mesh may not be centered at entity origin) and don't multiply by scale\n centerX = customBounds.center.x;\n centerY = customBounds.center.y;\n centerZ = customBounds.center.z;\n halfWidth = customBounds.halfExtents.x;\n halfHeight = customBounds.halfExtents.y;\n halfDepth = customBounds.halfExtents.z;\n }\n else {\n // Primitive shapes (box/sphere are 1x1x1 units, scale determines size)\n centerX = entityPos.x;\n centerY = entityPos.y;\n centerZ = entityPos.z;\n halfWidth = entityScale.x / 2;\n halfHeight = entityScale.y / 2;\n halfDepth = entityScale.z / 2;\n }\n const dx = Math.abs(position.x - centerX);\n const dy = Math.abs(position.y - centerY);\n const dz = Math.abs(position.z - centerZ);\n if (dx < halfWidth + radius &&\n dy < halfHeight + this.playerHeight / 2 &&\n dz < halfDepth + radius) {\n return true;\n }\n }\n return false;\n }\n /**\n * Check ground below player\n */\n private checkGround(position: pc.Vec3): number | null {\n // Check against floor entity if exists\n if (this.floorEntity) {\n const floorPos = this.floorEntity.getPosition();\n const floorScale = this.floorEntity.getLocalScale();\n // Check if we're above the floor plane\n const halfWidth = floorScale.x / 2 * 100; // Floors are typically large\n const halfDepth = floorScale.z / 2 * 100;\n if (Math.abs(position.x - floorPos.x) < halfWidth &&\n Math.abs(position.z - floorPos.z) < halfDepth) {\n return floorPos.y;\n }\n }\n // Check against other collision meshes for ground\n let highestGround: number | null = null;\n for (const entity of this.collisionEntities) {\n const entityPos = entity.getPosition();\n const entityScale = entity.getLocalScale();\n const meshType = (entity as CollisionEntity)._collisionMeshType;\n // Skip floor/plane (handled above) and sphere (not good for walking on)\n if (meshType === 'floor' || meshType === 'plane' || meshType === 'sphere') {\n continue;\n }\n let halfWidth: number, halfHeight: number, halfDepth: number;\n let centerX: number, centerY: number, centerZ: number;\n // Check if entity has custom bounds (for imported GLB/GLTF meshes)\n const customBounds = (entity as CollisionEntity)._collisionBounds;\n if (customBounds) {\n // World-space AABB - use bounds center and don't multiply by scale\n centerX = customBounds.center.x;\n centerY = customBounds.center.y;\n centerZ = customBounds.center.z;\n halfWidth = customBounds.halfExtents.x;\n halfHeight = customBounds.halfExtents.y;\n halfDepth = customBounds.halfExtents.z;\n }\n else {\n centerX = entityPos.x;\n centerY = entityPos.y;\n centerZ = entityPos.z;\n halfWidth = entityScale.x / 2;\n halfHeight = entityScale.y / 2;\n halfDepth = entityScale.z / 2;\n }\n const topY = centerY + halfHeight;\n // Check if we're above this mesh\n if (Math.abs(position.x - centerX) < halfWidth + this.collisionRadius &&\n Math.abs(position.z - centerZ) < halfDepth + this.collisionRadius &&\n position.y >= topY - this.stepHeight) {\n if (highestGround === null || topY > highestGround) {\n highestGround = topY;\n }\n }\n }\n return highestGround;\n }\n /**\n * Update the character controller - call this every frame\n */\n update(dt: number): void {\n if (!this.enabled)\n return;\n // Get movement input\n const moveX = (this.keys['KeyD'] || this.keys['ArrowRight'] ? 1 : 0) -\n (this.keys['KeyA'] || this.keys['ArrowLeft'] ? 1 : 0);\n const moveZ = (this.keys['KeyW'] || this.keys['ArrowUp'] ? 1 : 0) -\n (this.keys['KeyS'] || this.keys['ArrowDown'] ? 1 : 0);\n const isSprinting = this.keys['ShiftLeft'] || this.keys['ShiftRight'];\n // Calculate forward and right vectors (horizontal only)\n const yawRad = this.yaw * (Math.PI / 180);\n this.forward.set(-Math.sin(yawRad), 0, -Math.cos(yawRad));\n this.right.set(Math.cos(yawRad), 0, -Math.sin(yawRad));\n // Calculate target velocity based on input\n const speed = this.moveSpeed * (isSprinting ? this.sprintMultiplier : 1);\n this.targetVelocity.set(0, 0, 0);\n this.targetVelocity.add(this.tmpVec2.copy(this.forward).mulScalar(moveZ * speed));\n this.targetVelocity.add(this.tmpVec2.copy(this.right).mulScalar(moveX * speed));\n // Smooth the horizontal velocity toward target (creates ease-in/ease-out)\n const lerpFactor = damp(this.moveDamping, dt);\n this.horizontalVelocity.lerp(this.horizontalVelocity, this.targetVelocity, lerpFactor);\n // Apply gravity (vertical velocity is not smoothed for natural feel)\n if (!this.isGrounded) {\n this.velocity.y -= this.gravity * dt;\n this.velocity.y = Math.max(-this.maxFallSpeed, this.velocity.y);\n }\n // Get current position\n const currentPos = this.camera.getPosition().clone();\n const feetY = currentPos.y - this.playerHeight;\n // Calculate new position using smoothed horizontal velocity\n const newPos = new pc.Vec3();\n newPos.x = currentPos.x + this.horizontalVelocity.x * dt;\n newPos.y = currentPos.y + this.velocity.y * dt;\n newPos.z = currentPos.z + this.horizontalVelocity.z * dt;\n // Check horizontal collision (X axis)\n this.tmpVec2.set(newPos.x, currentPos.y, currentPos.z);\n if (this.checkCollision(this.tmpVec2, this.collisionRadius)) {\n newPos.x = currentPos.x;\n }\n // Check horizontal collision (Z axis)\n this.tmpVec2.set(newPos.x, currentPos.y, newPos.z);\n if (this.checkCollision(this.tmpVec2, this.collisionRadius)) {\n newPos.z = currentPos.z;\n }\n // Check ground\n const groundY = this.checkGround(newPos);\n const targetFeetY = newPos.y - this.playerHeight;\n if (groundY !== null && targetFeetY <= groundY + this.groundCheckDistance) {\n // On ground\n newPos.y = groundY + this.playerHeight;\n this.velocity.y = 0;\n this.isGrounded = true;\n }\n else if (groundY === null || targetFeetY > groundY + this.stepHeight) {\n // In air\n this.isGrounded = false;\n }\n // Apply new position\n this.camera.setPosition(newPos.x, newPos.y, newPos.z);\n // Apply rotation\n this.camera.setEulerAngles(this.pitch, this.yaw, 0);\n }\n /**\n * Clean up resources\n */\n destroy(): void {\n this.disable();\n // Remove collision entities\n for (const entity of this.collisionEntities) {\n entity.destroy();\n }\n this.collisionEntities = [];\n this.floorEntity = null;\n }\n /**\n * Get collision entities for use by other systems (e.g., CameraControls explore mode)\n */\n get collisionMeshEntities(): pc.Entity[] {\n return this.collisionEntities;\n }\n /**\n * Get whether the controller is grounded\n */\n get grounded(): boolean {\n return this.isGrounded;\n }\n /**\n * Get current velocity\n */\n getVelocity(): pc.Vec3 {\n return this.velocity.clone();\n }\n /**\n * Set position\n */\n setPosition(x: number, y: number, z: number): void {\n this.camera.setPosition(x, y, z);\n }\n /**\n * Set rotation (yaw and pitch in degrees)\n */\n setRotation(pitch: number, yaw: number): void {\n this.pitch = pitch;\n this.yaw = yaw;\n this.camera.setEulerAngles(pitch, yaw, 0);\n }\n}\n","/**\n * GsplatRevealRadial - Radial reveal effect for gaussian splats\n *\n * Creates two waves emanating from a center point:\n * 1. Dot wave: Small colored dots appear progressively\n * 2. Lift wave: Particles lift up, get highlighted, then settle to original state\n *\n * Ported from PlayCanvas engine examples\n */\nimport * as pc from 'playcanvas';\nconst shaderGLSL = /* glsl */ `\nuniform float uTime;\nuniform vec3 uCenter;\nuniform float uSpeed;\nuniform float uAcceleration;\nuniform float uDelay;\nuniform vec3 uDotTint;\nuniform vec3 uWaveTint;\nuniform float uOscillationIntensity;\nuniform float uEndRadius;\n\n// Shared globals (initialized once per vertex)\nfloat g_dist;\nfloat g_dotWavePos;\nfloat g_liftTime;\nfloat g_liftWavePos;\n\nvoid initShared(vec3 center) {\n g_dist = length(center - uCenter);\n g_dotWavePos = uSpeed * uTime + 0.5 * uAcceleration * uTime * uTime;\n g_liftTime = max(0.0, uTime - uDelay);\n g_liftWavePos = uSpeed * g_liftTime + 0.5 * uAcceleration * g_liftTime * g_liftTime;\n}\n\n// Hash function for per-splat randomization\nfloat hash(vec3 p) {\n return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453);\n}\n\nvoid modifyCenter(inout vec3 center) {\n initShared(center);\n\n // Early exit optimization\n if (g_dist > uEndRadius) return;\n\n // Only apply oscillation if lift wave hasn't fully passed\n bool wavesActive = g_liftTime <= 0.0 || g_dist > g_liftWavePos - 1.5;\n if (wavesActive) {\n // Apply oscillation with per-splat phase offset\n float phase = hash(center) * 6.28318;\n center.y += sin(uTime * 3.0 + phase) * uOscillationIntensity * 0.25;\n }\n\n // Apply lift effect near the wave edge\n float distToLiftWave = abs(g_dist - g_liftWavePos);\n if (distToLiftWave < 1.0 && g_liftTime > 0.0) {\n // Create a smooth lift curve (peaks at wave edge)\n // Lift is 0.9x the oscillation intensity (30% of original 3x)\n float liftAmount = (1.0 - distToLiftWave) * sin(distToLiftWave * 3.14159);\n center.y += liftAmount * uOscillationIntensity * 0.9;\n }\n}\n\nvoid modifyCovariance(vec3 originalCenter, vec3 modifiedCenter, inout vec3 covA, inout vec3 covB) {\n // Early exit for distant splats - hide them\n if (g_dist > uEndRadius) {\n gsplatMakeRound(covA, covB, 0.0);\n return;\n }\n\n // Determine scale and phase\n float scale;\n bool isLiftWave = g_liftTime > 0.0 && g_liftWavePos > g_dist;\n\n if (isLiftWave) {\n // Lift wave: transition from dots to full size\n scale = (g_liftWavePos >= g_dist + 2.0) ? 1.0 : mix(0.1, 1.0, (g_liftWavePos - g_dist) * 0.5);\n } else if (g_dist > g_dotWavePos + 1.0) {\n // Before dot wave: invisible\n gsplatMakeRound(covA, covB, 0.0);\n return;\n } else if (g_dist > g_dotWavePos - 1.0) {\n // Dot wave front: scale from 0 to 0.1 with 2x peak at center\n float distToWave = abs(g_dist - g_dotWavePos);\n scale = (distToWave < 0.5)\n ? mix(0.1, 0.2, 1.0 - distToWave * 2.0)\n : mix(0.0, 0.1, smoothstep(g_dotWavePos + 1.0, g_dotWavePos - 1.0, g_dist));\n } else {\n // After dot wave, before lift: small dots\n scale = 0.1;\n }\n\n // Apply scale to covariance\n if (scale >= 1.0) {\n // Fully revealed: original shape and size (no-op)\n return;\n } else if (isLiftWave) {\n // Lift wave: lerp from round dots to original shape\n float t = (scale - 0.1) * 1.111111; // normalize [0.1, 1.0] to [0, 1]\n float dotSize = scale * 0.05;\n float originalSize = gsplatExtractSize(covA, covB);\n float finalSize = mix(dotSize, originalSize, t);\n\n // Lerp between round and scaled original\n vec3 origCovA = covA * (scale * scale);\n vec3 origCovB = covB * (scale * scale);\n gsplatMakeRound(covA, covB, finalSize);\n covA = mix(covA, origCovA, t);\n covB = mix(covB, origCovB, t);\n } else {\n // Dot phase: round with absolute size, but don't make small splats larger\n float originalSize = gsplatExtractSize(covA, covB);\n gsplatMakeRound(covA, covB, min(scale * 0.05, originalSize));\n }\n}\n\nvoid modifyColor(vec3 center, inout vec4 color) {\n // Use shared globals\n if (g_dist > uEndRadius) return;\n\n // Lift wave tint takes priority (active during lift)\n if (g_liftTime > 0.0 && g_dist >= g_liftWavePos - 1.5 && g_dist <= g_liftWavePos + 0.5) {\n float distToLift = abs(g_dist - g_liftWavePos);\n float liftIntensity = smoothstep(1.5, 0.0, distToLift);\n color.rgb += uWaveTint * liftIntensity;\n }\n // Dot wave tint (active in dot phase, but not where lift wave is active)\n else if (g_dist <= g_dotWavePos && (g_liftTime <= 0.0 || g_dist > g_liftWavePos + 0.5)) {\n float distToDot = abs(g_dist - g_dotWavePos);\n float dotIntensity = smoothstep(1.0, 0.0, distToDot);\n color.rgb += uDotTint * dotIntensity;\n }\n}\n`;\nconst shaderWGSL = /* wgsl */ `\nuniform uTime: f32;\nuniform uCenter: vec3f;\nuniform uSpeed: f32;\nuniform uAcceleration: f32;\nuniform uDelay: f32;\nuniform uDotTint: vec3f;\nuniform uWaveTint: vec3f;\nuniform uOscillationIntensity: f32;\nuniform uEndRadius: f32;\n\n// Shared globals (initialized once per vertex)\nvar<private> g_dist: f32;\nvar<private> g_dotWavePos: f32;\nvar<private> g_liftTime: f32;\nvar<private> g_liftWavePos: f32;\n\nfn initShared(center: vec3f) {\n g_dist = length(center - uniform.uCenter);\n g_dotWavePos = uniform.uSpeed * uniform.uTime + 0.5 * uniform.uAcceleration * uniform.uTime * uniform.uTime;\n g_liftTime = max(0.0, uniform.uTime - uniform.uDelay);\n g_liftWavePos = uniform.uSpeed * g_liftTime + 0.5 * uniform.uAcceleration * g_liftTime * g_liftTime;\n}\n\n// Hash function for per-splat randomization\nfn hash(p: vec3f) -> f32 {\n return fract(sin(dot(p, vec3f(127.1, 311.7, 74.7))) * 43758.5453);\n}\n\nfn modifyCenter(center: ptr<function, vec3f>) {\n initShared(*center);\n\n // Early exit optimization\n if (g_dist > uniform.uEndRadius) {\n return;\n }\n\n // Only apply oscillation if lift wave hasn't fully passed\n let wavesActive = g_liftTime <= 0.0 || g_dist > g_liftWavePos - 1.5;\n if (wavesActive) {\n // Apply oscillation with per-splat phase offset\n let phase = hash(*center) * 6.28318;\n (*center).y += sin(uniform.uTime * 3.0 + phase) * uniform.uOscillationIntensity * 0.25;\n }\n\n // Apply lift effect near the wave edge\n let distToLiftWave = abs(g_dist - g_liftWavePos);\n if (distToLiftWave < 1.0 && g_liftTime > 0.0) {\n // Create a smooth lift curve (peaks at wave edge)\n // Lift is 0.9x the oscillation intensity (30% of original 3x)\n let liftAmount = (1.0 - distToLiftWave) * sin(distToLiftWave * 3.14159);\n (*center).y += liftAmount * uniform.uOscillationIntensity * 0.9;\n }\n}\n\nfn modifyCovariance(originalCenter: vec3f, modifiedCenter: vec3f, covA: ptr<function, vec3f>, covB: ptr<function, vec3f>) {\n // Early exit for distant splats - hide them\n if (g_dist > uniform.uEndRadius) {\n gsplatMakeRound(covA, covB, 0.0);\n return;\n }\n\n // Determine scale and phase\n var scale: f32;\n let isLiftWave = g_liftTime > 0.0 && g_liftWavePos > g_dist;\n\n if (isLiftWave) {\n // Lift wave: transition from dots to full size\n scale = select(mix(0.1, 1.0, (g_liftWavePos - g_dist) * 0.5), 1.0, g_liftWavePos >= g_dist + 2.0);\n } else if (g_dist > g_dotWavePos + 1.0) {\n // Before dot wave: invisible\n gsplatMakeRound(covA, covB, 0.0);\n return;\n } else if (g_dist > g_dotWavePos - 1.0) {\n // Dot wave front: scale from 0 to 0.1 with 2x peak at center\n let distToWave = abs(g_dist - g_dotWavePos);\n scale = select(\n mix(0.0, 0.1, smoothstep(g_dotWavePos + 1.0, g_dotWavePos - 1.0, g_dist)),\n mix(0.1, 0.2, 1.0 - distToWave * 2.0),\n distToWave < 0.5\n );\n } else {\n // After dot wave, before lift: small dots\n scale = 0.1;\n }\n\n // Apply scale to covariance\n if (scale >= 1.0) {\n // Fully revealed: original shape and size (no-op)\n return;\n } else if (isLiftWave) {\n // Lift wave: lerp from round dots to original shape\n let t = (scale - 0.1) * 1.111111; // normalize [0.1, 1.0] to [0, 1]\n let dotSize = scale * 0.05;\n let originalSize = gsplatExtractSize(*covA, *covB);\n let finalSize = mix(dotSize, originalSize, t);\n\n // Lerp between round and scaled original\n let origCovA = *covA * (scale * scale);\n let origCovB = *covB * (scale * scale);\n gsplatMakeRound(covA, covB, finalSize);\n *covA = mix(*covA, origCovA, t);\n *covB = mix(*covB, origCovB, t);\n } else {\n // Dot phase: round with absolute size, but don't make small splats larger\n let originalSize = gsplatExtractSize(*covA, *covB);\n gsplatMakeRound(covA, covB, min(scale * 0.05, originalSize));\n }\n}\n\nfn modifyColor(center: vec3f, color: ptr<function, vec4f>) {\n // Use shared globals\n if (g_dist > uniform.uEndRadius) {\n return;\n }\n\n // Lift wave tint takes priority (active during lift)\n if (g_liftTime > 0.0 && g_dist >= g_liftWavePos - 1.5 && g_dist <= g_liftWavePos + 0.5) {\n let distToLift = abs(g_dist - g_liftWavePos);\n let liftIntensity = smoothstep(1.5, 0.0, distToLift);\n (*color) = vec4f((*color).rgb + uniform.uWaveTint * liftIntensity, (*color).a);\n }\n // Dot wave tint (active in dot phase, but not where lift wave is active)\n else if (g_dist <= g_dotWavePos && (g_liftTime <= 0.0 || g_dist > g_liftWavePos + 0.5)) {\n let distToDot = abs(g_dist - g_dotWavePos);\n let dotIntensity = smoothstep(1.0, 0.0, distToDot);\n (*color) = vec4f((*color).rgb + uniform.uDotTint * dotIntensity, (*color).a);\n }\n}\n`;\n// Cache for the script class\nlet _GsplatRevealRadialClass: typeof pc.ScriptType | null = null;\ntype ScriptTypeWithPrototype<T extends pc.ScriptType> = typeof pc.ScriptType & {\n prototype: T;\n};\ntype ShaderChunkMaterial = pc.Material & {\n gsplat?: boolean;\n setShaderChunk?: (name: string, chunk: string) => void;\n chunks?: Record<string, string>;\n options?: unknown;\n shader?: unknown;\n};\ntype MaterialCollection = {\n size?: number;\n forEach: (callback: (material: pc.Material) => void) => void;\n};\ntype GSplatInstance = {\n constructor?: {\n name?: string;\n };\n materials?: MaterialCollection | pc.Material[];\n _materials?: MaterialCollection | pc.Material[];\n material?: pc.Material;\n _material?: pc.Material;\n on?: (event: string, callback: (material: pc.Material) => void) => void;\n off?: (event: string, callback: (material: pc.Material) => void) => void;\n};\ntype GSplatComponentWithRuntime = Omit<pc.GSplatComponent, '_instance'> & {\n unified?: boolean;\n instance?: GSplatInstance;\n _instance?: GSplatInstance;\n layers?: number[];\n asset?: unknown;\n};\ntype GSplatSystemWithRuntime = {\n on?: (event: string, callback: (material: pc.Material, camera: unknown, layer: unknown) => void) => void;\n off?: (event: string, callback: (material: pc.Material, camera: unknown, layer: unknown) => void) => void;\n getGSplatMaterial?: (camera: unknown, layer: unknown) => pc.Material | null | undefined;\n};\ntype RevealRuntimeEntity = Omit<pc.Entity, 'gsplat'> & {\n gsplat?: GSplatComponentWithRuntime;\n};\ninterface GsplatRevealRadialScript extends pc.ScriptType {\n effectTime: number;\n _materialsApplied: Set<pc.Material> | null;\n _shadersApplied: boolean;\n _retryCount: number;\n _maxRetries: number;\n _materialCreatedHandler: ((material: pc.Material) => void) | null;\n _systemMaterialHandler: ((material: pc.Material, camera: unknown, layer: unknown) => void) | null;\n _centerArray: [\n number,\n number,\n number\n ];\n _dotTintArray: [\n number,\n number,\n number\n ];\n _waveTintArray: [\n number,\n number,\n number\n ];\n center: pc.Vec3 | null;\n speed: number;\n acceleration: number;\n delay: number;\n dotTint: pc.Color | null;\n waveTint: pc.Color | null;\n oscillationIntensity: number;\n endRadius: number;\n getShaderGLSL: () => string;\n getShaderWGSL: () => string;\n _applyShaders: () => void;\n _applyToInstance: (instance: GSplatInstance) => void;\n _applyShaderToMaterial: (material: pc.Material) => void;\n _removeShaders: () => void;\n _updateUniforms: () => void;\n _setUniform: (name: string, value: number | number[]) => void;\n _getCompletionTime: () => number;\n _isEffectComplete: () => boolean;\n}\n/**\n * Gets or creates the GsplatRevealRadial script class.\n * Must be called after PlayCanvas app is initialized.\n */\nexport function getGsplatRevealRadialClass(): typeof pc.ScriptType {\n console.log('[RevealEffect] getGsplatRevealRadialClass called, cached:', !!_GsplatRevealRadialClass);\n if (_GsplatRevealRadialClass) {\n return _GsplatRevealRadialClass;\n }\n console.log('[RevealEffect] Creating new script class via pc.createScript');\n const GsplatRevealRadial = pc.createScript('gsplatRevealRadial') as ScriptTypeWithPrototype<GsplatRevealRadialScript>;\n console.log('[RevealEffect] Script class created:', GsplatRevealRadial);\n Object.assign(GsplatRevealRadial.prototype, {\n // Properties\n effectTime: 0,\n _materialsApplied: null as Set<pc.Material> | null,\n _shadersApplied: false,\n _retryCount: 0,\n _maxRetries: 100,\n _materialCreatedHandler: null as ((material: pc.Material) => void) | null,\n _systemMaterialHandler: null as ((material: pc.Material, camera: unknown, layer: unknown) => void) | null,\n // Reusable arrays for uniform updates\n _centerArray: [0, 0, 0],\n _dotTintArray: [0, 0, 0],\n _waveTintArray: [0, 0, 0],\n // Effect properties\n center: null as pc.Vec3 | null,\n speed: 5,\n acceleration: 0,\n delay: 2,\n dotTint: null as pc.Color | null,\n waveTint: null as pc.Color | null,\n oscillationIntensity: 0.2,\n endRadius: 25,\n initialize(this: GsplatRevealRadialScript) {\n console.log('[RevealEffect] initialize() called');\n this.effectTime = 0;\n this._materialsApplied = new Set();\n this._shadersApplied = false;\n this._retryCount = 0;\n this._centerArray = [0, 0, 0];\n this._dotTintArray = [0, 0, 0];\n this._waveTintArray = [0, 0, 0];\n // Initialize Vec3 and Color if not set\n if (!this.center) {\n this.center = new pc.Vec3(0, 0, 0);\n }\n if (!this.dotTint) {\n this.dotTint = new pc.Color(0, 1, 1); // Cyan\n }\n if (!this.waveTint) {\n this.waveTint = new pc.Color(1, 0.5, 0); // Orange\n }\n this.on('enable', () => {\n console.log('[RevealEffect] enabled event fired');\n this.effectTime = 0;\n this._applyShaders();\n });\n this.on('disable', () => {\n console.log('[RevealEffect] disabled event fired');\n this._removeShaders();\n });\n if (this.enabled) {\n console.log('[RevealEffect] Starting enabled, applying shaders');\n this._applyShaders();\n }\n else {\n console.log('[RevealEffect] Starting disabled, waiting for enable');\n }\n },\n update(this: GsplatRevealRadialScript, dt: number) {\n if (!this._shadersApplied && this._retryCount < this._maxRetries) {\n this._retryCount++;\n if (this._retryCount % 20 === 0) {\n console.log(`[RevealEffect] Retry ${this._retryCount}/${this._maxRetries} to apply shaders`);\n }\n this._applyShaders();\n }\n this.effectTime += dt;\n // Log every second\n if (Math.floor(this.effectTime) !== Math.floor(this.effectTime - dt)) {\n console.log(`[RevealEffect] effectTime: ${this.effectTime.toFixed(2)}s, shadersApplied: ${this._shadersApplied}, materialsCount: ${this._materialsApplied?.size || 0}`);\n }\n if (this._isEffectComplete()) {\n console.log('[RevealEffect] Effect complete, disabling');\n this.enabled = false;\n return;\n }\n this._updateUniforms();\n },\n _updateUniforms(this: GsplatRevealRadialScript) {\n this._setUniform('uTime', this.effectTime);\n this._centerArray[0] = this.center!.x;\n this._centerArray[1] = this.center!.y;\n this._centerArray[2] = this.center!.z;\n this._setUniform('uCenter', this._centerArray);\n this._setUniform('uSpeed', this.speed);\n this._setUniform('uAcceleration', this.acceleration);\n this._setUniform('uDelay', this.delay);\n this._dotTintArray[0] = this.dotTint!.r;\n this._dotTintArray[1] = this.dotTint!.g;\n this._dotTintArray[2] = this.dotTint!.b;\n this._setUniform('uDotTint', this._dotTintArray);\n this._waveTintArray[0] = this.waveTint!.r;\n this._waveTintArray[1] = this.waveTint!.g;\n this._waveTintArray[2] = this.waveTint!.b;\n this._setUniform('uWaveTint', this._waveTintArray);\n this._setUniform('uOscillationIntensity', this.oscillationIntensity);\n this._setUniform('uEndRadius', this.endRadius);\n },\n _getCompletionTime(this: GsplatRevealRadialScript): number {\n const liftStartTime = this.delay;\n if (this.acceleration === 0) {\n return liftStartTime + (this.endRadius / this.speed);\n }\n const discriminant = this.speed * this.speed + 2 * this.acceleration * this.endRadius;\n if (discriminant < 0) {\n return Infinity;\n }\n const t = (-this.speed + Math.sqrt(discriminant)) / this.acceleration;\n return liftStartTime + t;\n },\n _isEffectComplete(this: GsplatRevealRadialScript): boolean {\n return this.effectTime >= this._getCompletionTime();\n },\n getShaderGLSL(): string {\n return shaderGLSL;\n },\n getShaderWGSL(): string {\n return shaderWGSL;\n },\n // Shader application methods\n _applyShaders(this: GsplatRevealRadialScript) {\n const gsplatComponent = this.entity.gsplat;\n if (!gsplatComponent) {\n console.log('[RevealEffect] _applyShaders: No gsplat component found on entity');\n return;\n }\n // Check if unified mode is enabled\n const gsplatRuntime = gsplatComponent as unknown as GSplatComponentWithRuntime;\n const isUnified = gsplatRuntime.unified === true;\n const app = this.app;\n if (isUnified) {\n console.log('[RevealEffect] Unified mode detected, using GSplatComponentSystem');\n // In unified mode, materials are accessed via GSplatComponentSystem\n const gsplatSystem = app?.systems?.gsplat as GSplatSystemWithRuntime | undefined;\n if (gsplatSystem) {\n // Subscribe to material:created event if not already subscribed\n if (!this._systemMaterialHandler) {\n this._systemMaterialHandler = (material: pc.Material, camera: unknown, layer: unknown) => {\n console.log('[RevealEffect] material:created event from GSplatComponentSystem');\n this._applyShaderToMaterial(material);\n this._shadersApplied = true;\n };\n gsplatSystem.on?.('material:created', this._systemMaterialHandler);\n console.log('[RevealEffect] Subscribed to GSplatComponentSystem material:created event');\n }\n // Try to get existing materials for all camera/layer combinations\n const cameras = app.root?.findComponents('camera') || [];\n const layers = gsplatRuntime.layers || [0]; // Default to layer 0 if not specified\n for (const cameraComp of cameras) {\n for (const layerId of layers) {\n const layer = app.scene?.layers?.getLayerById(layerId);\n if (layer) {\n const material = gsplatSystem.getGSplatMaterial?.((cameraComp as unknown as { camera: unknown }).camera, layer);\n if (material) {\n console.log('[RevealEffect] Found unified material via getGSplatMaterial:', material);\n this._applyShaderToMaterial(material);\n this._shadersApplied = true;\n }\n }\n }\n }\n }\n return;\n }\n // Non-unified mode: Check both instance and _instance\n const instance = gsplatRuntime.instance || gsplatRuntime._instance;\n if (instance) {\n console.log('[RevealEffect] Instance found via component:', instance);\n this._applyToInstance(instance);\n return;\n }\n // Try to find gsplat materials through the scene's mesh instances\n if (app && app.scene) {\n // Search through layers for gsplat mesh instances\n const layers = app.scene.layers?.layerList || [];\n for (const layer of layers) {\n if (!layer.meshInstances)\n continue;\n for (const mi of layer.meshInstances) {\n // Check if this is a gsplat mesh instance\n const miRuntime = mi as pc.MeshInstance & { _gsplatInstance?: GSplatInstance };\n if (mi.gsplatInstance || miRuntime._gsplatInstance) {\n const gsplatInst = (mi.gsplatInstance || miRuntime._gsplatInstance) as GSplatInstance;\n console.log('[RevealEffect] Found gsplat instance via mesh instance:', gsplatInst);\n this._applyToInstance(gsplatInst);\n return;\n }\n // Check material for gsplat characteristics\n const material = mi.material;\n if (material && (material as ShaderChunkMaterial).gsplat) {\n console.log('[RevealEffect] Found gsplat material via mesh instance:', material);\n this._applyShaderToMaterial(material);\n this._shadersApplied = true;\n return;\n }\n }\n }\n // Also check root entities for gsplat components\n const checkEntity = (entity: pc.Entity): boolean => {\n const runtimeEntity = entity as RevealRuntimeEntity;\n if (runtimeEntity.gsplat) {\n const inst = runtimeEntity.gsplat.instance || runtimeEntity.gsplat._instance;\n if (inst) {\n console.log('[RevealEffect] Found gsplat instance via entity search:', inst);\n this._applyToInstance(inst);\n return true;\n }\n }\n for (const child of (entity.children || []) as pc.Entity[]) {\n if (checkEntity(child))\n return true;\n }\n return false;\n };\n if (app.root) {\n checkEntity(app.root);\n }\n }\n // Log state for debugging\n if (this._retryCount % 50 === 0) {\n console.log('[RevealEffect] Still searching for gsplat materials...');\n console.log('[RevealEffect] gsplatComponent.unified:', gsplatRuntime.unified);\n console.log('[RevealEffect] gsplatComponent.asset:', gsplatComponent.asset);\n }\n },\n _applyToInstance(this: GsplatRevealRadialScript, instance: GSplatInstance) {\n if (this._shadersApplied) {\n return;\n }\n console.log('[RevealEffect] Applying shaders to instance');\n console.log('[RevealEffect] Instance type:', instance.constructor?.name);\n console.log('[RevealEffect] Instance keys:', Object.keys(instance));\n // Try to find materials on the instance\n const materials = instance.materials || instance._materials;\n console.log('[RevealEffect] Instance materials:', materials);\n if (materials) {\n const materialsAsMC = materials as MaterialCollection;\n if (materials instanceof Map || (materialsAsMC.forEach && materialsAsMC.size !== undefined)) {\n console.log('[RevealEffect] Materials is a Map/Set with size:', materialsAsMC.size);\n if (materialsAsMC.size! > 0) {\n materialsAsMC.forEach((material: pc.Material) => {\n this._applyShaderToMaterial(material);\n });\n this._shadersApplied = true;\n console.log('[RevealEffect] SUCCESS: Shaders applied to', materialsAsMC.size, 'materials');\n }\n }\n else if (Array.isArray(materials)) {\n console.log('[RevealEffect] Materials is array with length:', materials.length);\n materials.forEach((material: pc.Material) => {\n this._applyShaderToMaterial(material);\n });\n this._shadersApplied = true;\n }\n }\n // Also try material property directly\n if (!this._shadersApplied) {\n const material = instance.material || instance._material;\n if (material) {\n console.log('[RevealEffect] Found single material on instance');\n this._applyShaderToMaterial(material);\n this._shadersApplied = true;\n }\n }\n // Subscribe to material:created for future materials\n if (instance.on && !this._materialCreatedHandler) {\n this._materialCreatedHandler = (material: pc.Material) => {\n console.log('[RevealEffect] material:created event received');\n this._applyShaderToMaterial(material);\n this._shadersApplied = true;\n };\n instance.on('material:created', this._materialCreatedHandler);\n console.log('[RevealEffect] Subscribed to material:created event');\n }\n },\n _applyShaderToMaterial(this: GsplatRevealRadialScript, material: pc.Material) {\n if (this._materialsApplied?.has(material)) {\n console.log('[RevealEffect] Material already has shader applied, skipping');\n return;\n }\n console.log('[RevealEffect] Applying shader to material:', material);\n console.log('[RevealEffect] Material constructor:', material.constructor?.name);\n console.log('[RevealEffect] Material keys:', Object.keys(material));\n // Log all methods on the material\n const methods: string[] = [];\n let obj: object | null = material;\n while (obj && obj !== Object.prototype) {\n const names = Object.getOwnPropertyNames(obj);\n for (const name of names) {\n try {\n const candidate = obj as Record<string, unknown>;\n if (typeof candidate[name] === 'function' && !methods.includes(name)) {\n methods.push(name);\n }\n }\n catch (_error) { }\n }\n obj = Object.getPrototypeOf(obj);\n }\n console.log('[RevealEffect] Material methods:', methods.filter(m => !m.startsWith('_')).join(', '));\n const glsl = this.getShaderGLSL();\n const wgsl = this.getShaderWGSL();\n // Try setShaderChunk first (newer API)\n const materialWithShaders = material as ShaderChunkMaterial;\n const hasSetShaderChunk = typeof materialWithShaders.setShaderChunk === 'function';\n console.log('[RevealEffect] material.setShaderChunk exists:', hasSetShaderChunk);\n if (hasSetShaderChunk) {\n if (glsl) {\n materialWithShaders.setShaderChunk?.('gsplatEffectGLSL', glsl);\n console.log('[RevealEffect] GLSL shader chunk set via setShaderChunk');\n }\n if (wgsl) {\n materialWithShaders.setShaderChunk?.('gsplatEffectWGSL', wgsl);\n console.log('[RevealEffect] WGSL shader chunk set via setShaderChunk');\n }\n }\n else {\n // Try alternative: chunks property (older API)\n if (materialWithShaders.chunks) {\n console.log('[RevealEffect] Material has chunks property');\n materialWithShaders.chunks.gsplatEffectGLSL = glsl;\n materialWithShaders.chunks.gsplatEffectWGSL = wgsl;\n console.log('[RevealEffect] Set chunks directly');\n }\n // Try: customFragmentShader or options\n if (materialWithShaders.options) {\n console.log('[RevealEffect] Material has options:', materialWithShaders.options);\n }\n // Try: shader property\n if (materialWithShaders.shader) {\n console.log('[RevealEffect] Material has shader:', materialWithShaders.shader);\n }\n // Try: setParameter for uniforms (this should work)\n if (typeof material.setParameter === 'function') {\n console.log('[RevealEffect] material.setParameter exists - will use for uniforms');\n }\n }\n material.update?.();\n this._materialsApplied?.add(material);\n console.log('[RevealEffect] Material added to applied set, total:', this._materialsApplied?.size);\n },\n _removeShaders(this: GsplatRevealRadialScript) {\n if (this._materialsApplied) {\n this._materialsApplied.forEach((material: pc.Material) => {\n const materialWithShaders = material as ShaderChunkMaterial;\n materialWithShaders.setShaderChunk?.('gsplatEffectGLSL', '');\n materialWithShaders.setShaderChunk?.('gsplatEffectWGSL', '');\n material.update?.();\n });\n this._materialsApplied.clear();\n }\n this._shadersApplied = false;\n // Clean up instance-level handler (non-unified mode)\n if (this._materialCreatedHandler) {\n const gsplatComponent = this.entity.gsplat;\n const gsplatInstance = (gsplatComponent as GSplatComponentWithRuntime | undefined)?.instance;\n if (gsplatInstance?.off) {\n gsplatInstance.off('material:created', this._materialCreatedHandler);\n }\n this._materialCreatedHandler = null;\n }\n // Clean up system-level handler (unified mode)\n if (this._systemMaterialHandler) {\n const gsplatSystem = this.app?.systems?.gsplat;\n if (gsplatSystem?.off) {\n gsplatSystem.off('material:created', this._systemMaterialHandler);\n }\n this._systemMaterialHandler = null;\n }\n },\n _setUniform(this: GsplatRevealRadialScript, name: string, value: number | number[]) {\n if (this._materialsApplied) {\n this._materialsApplied.forEach((material: pc.Material) => {\n material.setParameter?.(name, value);\n });\n }\n },\n destroy(this: GsplatRevealRadialScript) {\n this._removeShaders();\n }\n });\n _GsplatRevealRadialClass = GsplatRevealRadial;\n return GsplatRevealRadial;\n}\n// For backwards compatibility\nexport const GsplatRevealRadial = {\n get class() {\n return getGsplatRevealRadialClass();\n }\n};\n","/**\n * Reveal Effect Presets\n *\n * Pre-configured settings for the radial reveal effect.\n * Users can choose from fast, medium, or slow presets.\n */\n\nexport interface RevealPresetConfig {\n /** Base wave speed in units/second */\n speed: number;\n /** Speed increase over time */\n acceleration: number;\n /** Time offset before lift wave starts (seconds) */\n delay: number;\n /** Position oscillation strength */\n oscillationIntensity: number;\n /** Additive color for initial dots */\n dotTint: { r: number; g: number; b: number };\n /** Additive color for lift wave highlight */\n waveTint: { r: number; g: number; b: number };\n /** Distance at which to disable effect for performance */\n endRadius: number;\n}\n\n/**\n * Reveal effect presets\n */\nexport const REVEAL_PRESETS: Record<string, RevealPresetConfig> = {\n /**\n * Fast preset - Quick reveal for shorter loading experiences\n * Duration: ~3-4 seconds for a 50 unit radius scene\n */\n fast: {\n speed: 10,\n acceleration: 2,\n delay: 0.5,\n oscillationIntensity: 0.1,\n dotTint: { r: 0, g: 1, b: 1 }, // Cyan\n waveTint: { r: 1, g: 0.5, b: 0 }, // Orange\n endRadius: 50\n },\n\n /**\n * Medium preset - Balanced reveal for typical scenes\n * Duration: ~5-7 seconds for a 50 unit radius scene\n */\n medium: {\n speed: 5,\n acceleration: 0,\n delay: 2,\n oscillationIntensity: 0.2,\n dotTint: { r: 0, g: 1, b: 1 }, // Cyan\n waveTint: { r: 1, g: 0.5, b: 0 }, // Orange\n endRadius: 50\n },\n\n /**\n * Slow preset - Dramatic reveal for showcase/demo scenes\n * Duration: ~12-15 seconds for a 50 unit radius scene\n */\n slow: {\n speed: 3,\n acceleration: 0,\n delay: 3,\n oscillationIntensity: 0.25,\n dotTint: { r: 0, g: 1, b: 1 }, // Cyan\n waveTint: { r: 1, g: 0.5, b: 0 }, // Orange\n endRadius: 50\n }\n};\n\n/**\n * Type for reveal effect preset names\n */\nexport type RevealPreset = 'fast' | 'medium' | 'slow' | 'none';\n\n/**\n * Get preset configuration by name\n * Returns undefined for 'none' or invalid presets\n */\nexport function getRevealPreset(preset: RevealPreset): RevealPresetConfig | undefined {\n if (preset === 'none') {\n return undefined;\n }\n return REVEAL_PRESETS[preset];\n}\n","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.loop = exports.conditional = exports.parse = void 0;\n\nvar parse = function parse(stream, schema) {\n var result = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};\n var parent = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : result;\n\n if (Array.isArray(schema)) {\n schema.forEach(function (partSchema) {\n return parse(stream, partSchema, result, parent);\n });\n } else if (typeof schema === 'function') {\n schema(stream, result, parent, parse);\n } else {\n var key = Object.keys(schema)[0];\n\n if (Array.isArray(schema[key])) {\n parent[key] = {};\n parse(stream, schema[key], result, parent[key]);\n } else {\n parent[key] = schema[key](stream, result, parent, parse);\n }\n }\n\n return result;\n};\n\nexports.parse = parse;\n\nvar conditional = function conditional(schema, conditionFunc) {\n return function (stream, result, parent, parse) {\n if (conditionFunc(stream, result, parent)) {\n parse(stream, schema, result, parent);\n }\n };\n};\n\nexports.conditional = conditional;\n\nvar loop = function loop(schema, continueFunc) {\n return function (stream, result, parent, parse) {\n var arr = [];\n var lastStreamPos = stream.pos;\n\n while (continueFunc(stream, result, parent)) {\n var newParent = {};\n parse(stream, schema, result, newParent); // cases when whole file is parsed but no termination is there and stream position is not getting updated as well\n // it falls into infinite recursion, null check to avoid the same\n\n if (stream.pos === lastStreamPos) {\n break;\n }\n\n lastStreamPos = stream.pos;\n arr.push(newParent);\n }\n\n return arr;\n };\n};\n\nexports.loop = loop;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.readBits = exports.readArray = exports.readUnsigned = exports.readString = exports.peekBytes = exports.readBytes = exports.peekByte = exports.readByte = exports.buildStream = void 0;\n\n// Default stream and parsers for Uint8TypedArray data type\nvar buildStream = function buildStream(uint8Data) {\n return {\n data: uint8Data,\n pos: 0\n };\n};\n\nexports.buildStream = buildStream;\n\nvar readByte = function readByte() {\n return function (stream) {\n return stream.data[stream.pos++];\n };\n};\n\nexports.readByte = readByte;\n\nvar peekByte = function peekByte() {\n var offset = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;\n return function (stream) {\n return stream.data[stream.pos + offset];\n };\n};\n\nexports.peekByte = peekByte;\n\nvar readBytes = function readBytes(length) {\n return function (stream) {\n return stream.data.subarray(stream.pos, stream.pos += length);\n };\n};\n\nexports.readBytes = readBytes;\n\nvar peekBytes = function peekBytes(length) {\n return function (stream) {\n return stream.data.subarray(stream.pos, stream.pos + length);\n };\n};\n\nexports.peekBytes = peekBytes;\n\nvar readString = function readString(length) {\n return function (stream) {\n return Array.from(readBytes(length)(stream)).map(function (value) {\n return String.fromCharCode(value);\n }).join('');\n };\n};\n\nexports.readString = readString;\n\nvar readUnsigned = function readUnsigned(littleEndian) {\n return function (stream) {\n var bytes = readBytes(2)(stream);\n return littleEndian ? (bytes[1] << 8) + bytes[0] : (bytes[0] << 8) + bytes[1];\n };\n};\n\nexports.readUnsigned = readUnsigned;\n\nvar readArray = function readArray(byteSize, totalOrFunc) {\n return function (stream, result, parent) {\n var total = typeof totalOrFunc === 'function' ? totalOrFunc(stream, result, parent) : totalOrFunc;\n var parser = readBytes(byteSize);\n var arr = new Array(total);\n\n for (var i = 0; i < total; i++) {\n arr[i] = parser(stream);\n }\n\n return arr;\n };\n};\n\nexports.readArray = readArray;\n\nvar subBitsTotal = function subBitsTotal(bits, startIndex, length) {\n var result = 0;\n\n for (var i = 0; i < length; i++) {\n result += bits[startIndex + i] && Math.pow(2, length - i - 1);\n }\n\n return result;\n};\n\nvar readBits = function readBits(schema) {\n return function (stream) {\n var _byte = readByte()(stream); // convert the byte to bit array\n\n\n var bits = new Array(8);\n\n for (var i = 0; i < 8; i++) {\n bits[7 - i] = !!(_byte & 1 << i);\n } // convert the bit array to values based on the schema\n\n\n return Object.keys(schema).reduce(function (res, key) {\n var def = schema[key];\n\n if (def.length) {\n res[key] = subBitsTotal(bits, def.index, def.length);\n } else {\n res[key] = bits[def.index];\n }\n\n return res;\n }, {});\n };\n};\n\nexports.readBits = readBits;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.decompressFrames = exports.decompressFrame = exports.parseGIF = void 0;\n\nvar _gif = _interopRequireDefault(require(\"js-binary-schema-parser/lib/schemas/gif\"));\n\nvar _jsBinarySchemaParser = require(\"js-binary-schema-parser\");\n\nvar _uint = require(\"js-binary-schema-parser/lib/parsers/uint8\");\n\nvar _deinterlace = require(\"./deinterlace\");\n\nvar _lzw = require(\"./lzw\");\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\n\nvar parseGIF = function parseGIF(arrayBuffer) {\n var byteData = new Uint8Array(arrayBuffer);\n return (0, _jsBinarySchemaParser.parse)((0, _uint.buildStream)(byteData), _gif[\"default\"]);\n};\n\nexports.parseGIF = parseGIF;\n\nvar generatePatch = function generatePatch(image) {\n var totalPixels = image.pixels.length;\n var patchData = new Uint8ClampedArray(totalPixels * 4);\n\n for (var i = 0; i < totalPixels; i++) {\n var pos = i * 4;\n var colorIndex = image.pixels[i];\n var color = image.colorTable[colorIndex] || [0, 0, 0];\n patchData[pos] = color[0];\n patchData[pos + 1] = color[1];\n patchData[pos + 2] = color[2];\n patchData[pos + 3] = colorIndex !== image.transparentIndex ? 255 : 0;\n }\n\n return patchData;\n};\n\nvar decompressFrame = function decompressFrame(frame, gct, buildImagePatch) {\n if (!frame.image) {\n console.warn('gif frame does not have associated image.');\n return;\n }\n\n var image = frame.image; // get the number of pixels\n\n var totalPixels = image.descriptor.width * image.descriptor.height; // do lzw decompression\n\n var pixels = (0, _lzw.lzw)(image.data.minCodeSize, image.data.blocks, totalPixels); // deal with interlacing if necessary\n\n if (image.descriptor.lct.interlaced) {\n pixels = (0, _deinterlace.deinterlace)(pixels, image.descriptor.width);\n }\n\n var resultImage = {\n pixels: pixels,\n dims: {\n top: frame.image.descriptor.top,\n left: frame.image.descriptor.left,\n width: frame.image.descriptor.width,\n height: frame.image.descriptor.height\n }\n }; // color table\n\n if (image.descriptor.lct && image.descriptor.lct.exists) {\n resultImage.colorTable = image.lct;\n } else {\n resultImage.colorTable = gct;\n } // add per frame relevant gce information\n\n\n if (frame.gce) {\n resultImage.delay = (frame.gce.delay || 10) * 10; // convert to ms\n\n resultImage.disposalType = frame.gce.extras.disposal; // transparency\n\n if (frame.gce.extras.transparentColorGiven) {\n resultImage.transparentIndex = frame.gce.transparentColorIndex;\n }\n } // create canvas usable imagedata if desired\n\n\n if (buildImagePatch) {\n resultImage.patch = generatePatch(resultImage);\n }\n\n return resultImage;\n};\n\nexports.decompressFrame = decompressFrame;\n\nvar decompressFrames = function decompressFrames(parsedGif, buildImagePatches) {\n return parsedGif.frames.filter(function (f) {\n return f.image;\n }).map(function (f) {\n return decompressFrame(f, parsedGif.gct, buildImagePatches);\n });\n};\n\nexports.decompressFrames = decompressFrames;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports[\"default\"] = void 0;\n\nvar _ = require(\"../\");\n\nvar _uint = require(\"../parsers/uint8\");\n\n// a set of 0x00 terminated subblocks\nvar subBlocksSchema = {\n blocks: function blocks(stream) {\n var terminator = 0x00;\n var chunks = [];\n var streamSize = stream.data.length;\n var total = 0;\n\n for (var size = (0, _uint.readByte)()(stream); size !== terminator; size = (0, _uint.readByte)()(stream)) {\n // size becomes undefined for some case when file is corrupted and terminator is not proper \n // null check to avoid recursion\n if (!size) break; // catch corrupted files with no terminator\n\n if (stream.pos + size >= streamSize) {\n var availableSize = streamSize - stream.pos;\n chunks.push((0, _uint.readBytes)(availableSize)(stream));\n total += availableSize;\n break;\n }\n\n chunks.push((0, _uint.readBytes)(size)(stream));\n total += size;\n }\n\n var result = new Uint8Array(total);\n var offset = 0;\n\n for (var i = 0; i < chunks.length; i++) {\n result.set(chunks[i], offset);\n offset += chunks[i].length;\n }\n\n return result;\n }\n}; // global control extension\n\nvar gceSchema = (0, _.conditional)({\n gce: [{\n codes: (0, _uint.readBytes)(2)\n }, {\n byteSize: (0, _uint.readByte)()\n }, {\n extras: (0, _uint.readBits)({\n future: {\n index: 0,\n length: 3\n },\n disposal: {\n index: 3,\n length: 3\n },\n userInput: {\n index: 6\n },\n transparentColorGiven: {\n index: 7\n }\n })\n }, {\n delay: (0, _uint.readUnsigned)(true)\n }, {\n transparentColorIndex: (0, _uint.readByte)()\n }, {\n terminator: (0, _uint.readByte)()\n }]\n}, function (stream) {\n var codes = (0, _uint.peekBytes)(2)(stream);\n return codes[0] === 0x21 && codes[1] === 0xf9;\n}); // image pipeline block\n\nvar imageSchema = (0, _.conditional)({\n image: [{\n code: (0, _uint.readByte)()\n }, {\n descriptor: [{\n left: (0, _uint.readUnsigned)(true)\n }, {\n top: (0, _uint.readUnsigned)(true)\n }, {\n width: (0, _uint.readUnsigned)(true)\n }, {\n height: (0, _uint.readUnsigned)(true)\n }, {\n lct: (0, _uint.readBits)({\n exists: {\n index: 0\n },\n interlaced: {\n index: 1\n },\n sort: {\n index: 2\n },\n future: {\n index: 3,\n length: 2\n },\n size: {\n index: 5,\n length: 3\n }\n })\n }]\n }, (0, _.conditional)({\n lct: (0, _uint.readArray)(3, function (stream, result, parent) {\n return Math.pow(2, parent.descriptor.lct.size + 1);\n })\n }, function (stream, result, parent) {\n return parent.descriptor.lct.exists;\n }), {\n data: [{\n minCodeSize: (0, _uint.readByte)()\n }, subBlocksSchema]\n }]\n}, function (stream) {\n return (0, _uint.peekByte)()(stream) === 0x2c;\n}); // plain text block\n\nvar textSchema = (0, _.conditional)({\n text: [{\n codes: (0, _uint.readBytes)(2)\n }, {\n blockSize: (0, _uint.readByte)()\n }, {\n preData: function preData(stream, result, parent) {\n return (0, _uint.readBytes)(parent.text.blockSize)(stream);\n }\n }, subBlocksSchema]\n}, function (stream) {\n var codes = (0, _uint.peekBytes)(2)(stream);\n return codes[0] === 0x21 && codes[1] === 0x01;\n}); // application block\n\nvar applicationSchema = (0, _.conditional)({\n application: [{\n codes: (0, _uint.readBytes)(2)\n }, {\n blockSize: (0, _uint.readByte)()\n }, {\n id: function id(stream, result, parent) {\n return (0, _uint.readString)(parent.blockSize)(stream);\n }\n }, subBlocksSchema]\n}, function (stream) {\n var codes = (0, _uint.peekBytes)(2)(stream);\n return codes[0] === 0x21 && codes[1] === 0xff;\n}); // comment block\n\nvar commentSchema = (0, _.conditional)({\n comment: [{\n codes: (0, _uint.readBytes)(2)\n }, subBlocksSchema]\n}, function (stream) {\n var codes = (0, _uint.peekBytes)(2)(stream);\n return codes[0] === 0x21 && codes[1] === 0xfe;\n});\nvar schema = [{\n header: [{\n signature: (0, _uint.readString)(3)\n }, {\n version: (0, _uint.readString)(3)\n }]\n}, {\n lsd: [{\n width: (0, _uint.readUnsigned)(true)\n }, {\n height: (0, _uint.readUnsigned)(true)\n }, {\n gct: (0, _uint.readBits)({\n exists: {\n index: 0\n },\n resolution: {\n index: 1,\n length: 3\n },\n sort: {\n index: 4\n },\n size: {\n index: 5,\n length: 3\n }\n })\n }, {\n backgroundColorIndex: (0, _uint.readByte)()\n }, {\n pixelAspectRatio: (0, _uint.readByte)()\n }]\n}, (0, _.conditional)({\n gct: (0, _uint.readArray)(3, function (stream, result) {\n return Math.pow(2, result.lsd.gct.size + 1);\n })\n}, function (stream, result) {\n return result.lsd.gct.exists;\n}), // content frames\n{\n frames: (0, _.loop)([gceSchema, applicationSchema, commentSchema, imageSchema, textSchema], function (stream) {\n var nextCode = (0, _uint.peekByte)()(stream); // rather than check for a terminator, we should check for the existence\n // of an ext or image block to avoid infinite loops\n //var terminator = 0x3B;\n //return nextCode !== terminator;\n\n return nextCode === 0x21 || nextCode === 0x2c;\n })\n}];\nvar _default = schema;\nexports[\"default\"] = _default;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.deinterlace = void 0;\n\n/**\r\n * Deinterlace function from https://github.com/shachaf/jsgif\r\n */\nvar deinterlace = function deinterlace(pixels, width) {\n var newPixels = new Array(pixels.length);\n var rows = pixels.length / width;\n\n var cpRow = function cpRow(toRow, fromRow) {\n var fromPixels = pixels.slice(fromRow * width, (fromRow + 1) * width);\n newPixels.splice.apply(newPixels, [toRow * width, width].concat(fromPixels));\n }; // See appendix E.\n\n\n var offsets = [0, 4, 2, 1];\n var steps = [8, 8, 4, 2];\n var fromRow = 0;\n\n for (var pass = 0; pass < 4; pass++) {\n for (var toRow = offsets[pass]; toRow < rows; toRow += steps[pass]) {\n cpRow(toRow, fromRow);\n fromRow++;\n }\n }\n\n return newPixels;\n};\n\nexports.deinterlace = deinterlace;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.lzw = void 0;\n\n/**\r\n * javascript port of java LZW decompression\r\n * Original java author url: https://gist.github.com/devunwired/4479231\r\n */\nvar lzw = function lzw(minCodeSize, data, pixelCount) {\n var MAX_STACK_SIZE = 4096;\n var nullCode = -1;\n var npix = pixelCount;\n var available, clear, code_mask, code_size, end_of_information, in_code, old_code, bits, code, i, datum, data_size, first, top, bi, pi;\n var dstPixels = new Array(pixelCount);\n var prefix = new Array(MAX_STACK_SIZE);\n var suffix = new Array(MAX_STACK_SIZE);\n var pixelStack = new Array(MAX_STACK_SIZE + 1); // Initialize GIF data stream decoder.\n\n data_size = minCodeSize;\n clear = 1 << data_size;\n end_of_information = clear + 1;\n available = clear + 2;\n old_code = nullCode;\n code_size = data_size + 1;\n code_mask = (1 << code_size) - 1;\n\n for (code = 0; code < clear; code++) {\n prefix[code] = 0;\n suffix[code] = code;\n } // Decode GIF pixel stream.\n\n\n var datum, bits, count, first, top, pi, bi;\n datum = bits = count = first = top = pi = bi = 0;\n\n for (i = 0; i < npix;) {\n if (top === 0) {\n if (bits < code_size) {\n // get the next byte\n datum += data[bi] << bits;\n bits += 8;\n bi++;\n continue;\n } // Get the next code.\n\n\n code = datum & code_mask;\n datum >>= code_size;\n bits -= code_size; // Interpret the code\n\n if (code > available || code == end_of_information) {\n break;\n }\n\n if (code == clear) {\n // Reset decoder.\n code_size = data_size + 1;\n code_mask = (1 << code_size) - 1;\n available = clear + 2;\n old_code = nullCode;\n continue;\n }\n\n if (old_code == nullCode) {\n pixelStack[top++] = suffix[code];\n old_code = code;\n first = code;\n continue;\n }\n\n in_code = code;\n\n if (code == available) {\n pixelStack[top++] = first;\n code = old_code;\n }\n\n while (code > clear) {\n pixelStack[top++] = suffix[code];\n code = prefix[code];\n }\n\n first = suffix[code] & 0xff;\n pixelStack[top++] = first; // add a new string to the table, but only if space is available\n // if not, just continue with current table until a clear code is found\n // (deferred clear code implementation as per GIF spec)\n\n if (available < MAX_STACK_SIZE) {\n prefix[available] = old_code;\n suffix[available] = first;\n available++;\n\n if ((available & code_mask) === 0 && available < MAX_STACK_SIZE) {\n code_size++;\n code_mask += available;\n }\n }\n\n old_code = in_code;\n } // Pop a pixel off the pixel stack.\n\n\n top--;\n dstPixels[pi++] = pixelStack[top];\n i++;\n }\n\n for (i = pi; i < npix; i++) {\n dstPixels[i] = 0; // clear missing pixels\n }\n\n return dstPixels;\n};\n\nexports.lzw = lzw;","/**\n * Animated GIF Helper for PlayCanvas\n *\n * Uses gifuct-js to parse and decompress GIF frames, then renders them\n * to a PlayCanvas texture with proper transparency support.\n */\nimport * as pc from 'playcanvas';\nimport { parseGIF, decompressFrames } from 'gifuct-js';\n/**\n * Represents a single GIF frame\n */\ninterface GifFrame {\n dims: {\n width: number;\n height: number;\n top: number;\n left: number;\n };\n patch: Uint8ClampedArray;\n delay: number;\n disposalType?: number;\n}\n/**\n * Options for creating an animated GIF texture\n */\nexport interface AnimatedGifOptions {\n /** Auto-play the GIF when loaded */\n autoPlay?: boolean;\n /** Callback when the first frame is ready */\n onReady?: () => void;\n /** Callback on error */\n onError?: (error: Error) => void;\n}\n/**\n * Animated GIF texture for PlayCanvas\n * Handles parsing, decompression, and frame-by-frame rendering with transparency\n */\nexport class AnimatedGifTexture {\n private app: pc.Application;\n private url: string;\n private frames: GifFrame[] = [];\n private currentFrameIndex = 0;\n private isPlaying = false;\n private isLoaded = false;\n private lastFrameTime = 0;\n private updateHandler: (() => void) | null = null;\n private options: AnimatedGifOptions;\n // Canvas for compositing frames (handles disposal and transparency)\n private canvas: HTMLCanvasElement;\n private ctx: CanvasRenderingContext2D;\n // PlayCanvas texture\n public texture: pc.Texture | null = null;\n // GIF dimensions\n private gifWidth = 0;\n private gifHeight = 0;\n constructor(app: pc.Application, url: string, options: AnimatedGifOptions = {}) {\n this.app = app;\n this.url = url;\n this.options = options;\n // Create offscreen canvas for compositing\n this.canvas = document.createElement('canvas');\n this.ctx = this.canvas.getContext('2d', { willReadFrequently: true })!;\n // Start loading\n this.load();\n }\n /**\n * Load and parse the GIF\n */\n private async load(): Promise<void> {\n try {\n // Fetch GIF data\n const response = await fetch(this.url);\n if (!response.ok) {\n throw new Error(`Failed to fetch GIF: ${response.statusText}`);\n }\n const buffer = await response.arrayBuffer();\n // Parse GIF\n const gif = parseGIF(buffer);\n this.frames = decompressFrames(gif, true) as unknown as GifFrame[];\n if (this.frames.length === 0) {\n throw new Error('GIF has no frames');\n }\n // Get dimensions from first frame\n this.gifWidth = gif.lsd.width;\n this.gifHeight = gif.lsd.height;\n // Setup canvas\n this.canvas.width = this.gifWidth;\n this.canvas.height = this.gifHeight;\n // Create PlayCanvas texture\n this.texture = new pc.Texture(this.app.graphicsDevice, {\n width: this.gifWidth,\n height: this.gifHeight,\n format: pc.PIXELFORMAT_RGBA8,\n mipmaps: false,\n minFilter: pc.FILTER_LINEAR,\n magFilter: pc.FILTER_LINEAR,\n addressU: pc.ADDRESS_CLAMP_TO_EDGE,\n addressV: pc.ADDRESS_CLAMP_TO_EDGE\n });\n // Draw first frame\n this.drawFrame(0);\n this.isLoaded = true;\n console.log(`[AnimatedGif] Loaded GIF: ${this.url}, ${this.frames.length} frames, ${this.gifWidth}x${this.gifHeight}`);\n // Notify ready\n if (this.options.onReady) {\n this.options.onReady();\n }\n // Auto-play if requested\n if (this.options.autoPlay) {\n this.play();\n }\n }\n catch (error) {\n console.error('[AnimatedGif] Error loading GIF:', error);\n if (this.options.onError) {\n this.options.onError(error instanceof Error ? error : new Error(String(error)));\n }\n }\n }\n /**\n * Draw a specific frame to the canvas and update the texture\n */\n private drawFrame(frameIndex: number): void {\n if (!this.texture || frameIndex >= this.frames.length)\n return;\n const frame = this.frames[frameIndex];\n const prevFrame = frameIndex > 0 ? this.frames[frameIndex - 1] : null;\n // Handle disposal of previous frame\n if (prevFrame && prevFrame.disposalType === 2) {\n // Restore to background (clear the previous frame area)\n this.ctx.clearRect(prevFrame.dims.left, prevFrame.dims.top, prevFrame.dims.width, prevFrame.dims.height);\n }\n // disposalType === 3 (restore to previous) is complex and rarely used, skip for now\n // Create ImageData from frame patch\n const imageData = new ImageData(new Uint8ClampedArray(frame.patch), frame.dims.width, frame.dims.height);\n // Create temporary canvas for this frame\n const tempCanvas = document.createElement('canvas');\n tempCanvas.width = frame.dims.width;\n tempCanvas.height = frame.dims.height;\n const tempCtx = tempCanvas.getContext('2d')!;\n tempCtx.putImageData(imageData, 0, 0);\n // Draw frame patch at correct position\n this.ctx.drawImage(tempCanvas, frame.dims.left, frame.dims.top);\n // Update texture from canvas\n this.updateTexture();\n }\n /**\n * Update the PlayCanvas texture from the canvas\n */\n private updateTexture(): void {\n if (!this.texture)\n return;\n // Get pixel data from canvas\n const imageData = this.ctx.getImageData(0, 0, this.gifWidth, this.gifHeight);\n // Upload to texture\n const pixels = this.texture.lock();\n if (pixels) {\n pixels.set(imageData.data);\n }\n this.texture.unlock();\n this.texture.upload();\n }\n /**\n * Animation update - called each frame when playing\n */\n private update = (): void => {\n if (!this.isPlaying || !this.isLoaded || this.frames.length <= 1)\n return;\n const now = performance.now();\n const frame = this.frames[this.currentFrameIndex];\n const delay = frame.delay || 100; // Default 100ms if not specified\n if (now - this.lastFrameTime >= delay) {\n // Advance to next frame\n this.currentFrameIndex = (this.currentFrameIndex + 1) % this.frames.length;\n this.drawFrame(this.currentFrameIndex);\n this.lastFrameTime = now;\n }\n };\n /**\n * Start playing the GIF animation\n */\n public play(): void {\n if (this.isPlaying)\n return;\n this.isPlaying = true;\n this.lastFrameTime = performance.now();\n // Register update handler\n if (!this.updateHandler) {\n this.updateHandler = this.update;\n this.app.on('update', this.updateHandler);\n }\n }\n /**\n * Pause the GIF animation\n */\n public pause(): void {\n if (!this.isPlaying)\n return;\n this.isPlaying = false;\n // Remove update handler\n if (this.updateHandler) {\n this.app.off('update', this.updateHandler);\n this.updateHandler = null;\n }\n }\n /**\n * Stop and reset to first frame\n */\n public stop(): void {\n this.pause();\n this.currentFrameIndex = 0;\n if (this.isLoaded) {\n // Clear canvas and redraw first frame\n this.ctx.clearRect(0, 0, this.gifWidth, this.gifHeight);\n this.drawFrame(0);\n }\n }\n /**\n * Check if the GIF is currently playing\n */\n public get playing(): boolean {\n return this.isPlaying;\n }\n /**\n * Check if the GIF is loaded\n */\n public get loaded(): boolean {\n return this.isLoaded;\n }\n /**\n * Clean up resources\n */\n public destroy(): void {\n this.pause();\n if (this.texture) {\n this.texture.destroy();\n this.texture = null;\n }\n this.frames = [];\n this.isLoaded = false;\n this.canvas = null!;\n this.ctx = null!;\n }\n}\n/**\n * Helper to detect if a URL is a GIF\n */\nexport function isGifUrl(url: string): boolean {\n const lower = url.toLowerCase();\n // Check extension or content type hint\n return lower.endsWith('.gif') || lower.includes('image/gif') || lower.includes('format=gif');\n}\n","/**\n * HTML Mesh Helper for PlayCanvas\n *\n * Renders HTML elements as textures on 3D meshes using the texElement2D API\n * (HTML-in-Canvas proposal) with fallback to canvas rendering.\n *\n * Based on PlayCanvas PR #7897: https://github.com/playcanvas/engine/pull/7897\n */\nimport * as pc from 'playcanvas';\ntype GraphicsDeviceWithHtmlSupport = pc.GraphicsDevice & {\n supportsTexElement2D?: boolean;\n _isHTMLElementInterface?: (texture: unknown) => boolean;\n _isBrowserInterface?: (texture: unknown) => boolean;\n};\ninterface GraphicsDeviceConstructorWithHtmlSupport {\n prototype: GraphicsDeviceWithHtmlSupport;\n}\ninterface TextureWithHTMLElementSource extends pc.Texture {\n setSource(source: unknown): void;\n}\ninterface HtmlMeshEntity extends pc.Entity {\n _billboardActive?: boolean;\n}\n/**\n * Patch PlayCanvas GraphicsDevice to add _isHTMLElementInterface method\n * This is required for HTML-in-Canvas support (texElement2D API)\n *\n * The postinstall patches only modify ESM source files, but production builds\n * may use the bundled playcanvas.js which doesn't have the patches.\n * This runtime patch ensures the method exists regardless of which build is used.\n */\nfunction patchPlayCanvasForHtmlMesh(): void {\n // Get the GraphicsDevice class from pc\n const GraphicsDevice = (pc as typeof pc & {\n GraphicsDevice?: GraphicsDeviceConstructorWithHtmlSupport;\n }).GraphicsDevice;\n if (!GraphicsDevice) {\n console.warn('[HtmlMeshHelper] Could not find pc.GraphicsDevice to patch');\n return;\n }\n // Check if already patched\n if (typeof GraphicsDevice.prototype._isHTMLElementInterface === 'function') {\n return; // Already patched\n }\n // Add the _isHTMLElementInterface method\n GraphicsDevice.prototype._isHTMLElementInterface = function (texture: unknown): boolean {\n return typeof HTMLElement !== 'undefined' &&\n texture instanceof HTMLElement &&\n !(texture instanceof HTMLImageElement) &&\n !(texture instanceof HTMLCanvasElement) &&\n !(texture instanceof HTMLVideoElement);\n };\n // Patch _isBrowserInterface to include our new method\n const originalIsBrowserInterface = GraphicsDevice.prototype._isBrowserInterface;\n if (originalIsBrowserInterface) {\n GraphicsDevice.prototype._isBrowserInterface = function (texture: unknown): boolean {\n return originalIsBrowserInterface.call(this, texture) ||\n this._isHTMLElementInterface!(texture);\n };\n }\n console.log('[HtmlMeshHelper] Patched PlayCanvas GraphicsDevice for HTML-in-Canvas support');\n}\n/**\n * HTML Mesh configuration\n */\nexport interface HtmlMeshConfig {\n /** Unique ID for the mesh */\n id: string;\n /** HTML content to render */\n html?: string;\n /** Raw HTML content (alias for html) */\n htmlContent?: string;\n /** CSS styles for the container (can reference external stylesheets) */\n css?: string;\n /** Position in world space */\n position: {\n x: number;\n y: number;\n z: number;\n };\n /** Rotation in degrees */\n rotation?: {\n x: number;\n y: number;\n z: number;\n };\n /** Scale of the mesh */\n scale?: {\n x: number;\n y: number;\n z: number;\n };\n /** Width in pixels (default: 512) */\n width?: number;\n /** Height in pixels (default: 512) */\n height?: number;\n /** Whether to update the texture every frame (for animations) */\n animated?: boolean;\n /** Update rate in milliseconds (default: 100ms for animated) */\n updateRate?: number;\n /** Whether to face the camera (billboard mode) */\n billboard?: boolean;\n /** Billboard range control (percentage or waypoint based) */\n billboardRange?: {\n type: 'percentage' | 'waypoint';\n start: number;\n end: number;\n };\n /** Visibility range control */\n visibilityRange?: {\n type: 'percentage' | 'waypoint';\n start: number;\n end: number;\n };\n /** Whether to receive shadows */\n receiveShadows?: boolean;\n /** Whether to cast shadows */\n castShadows?: boolean;\n /** Opacity (0-1) */\n opacity?: number;\n /** Whether the mesh is double-sided */\n doubleSided?: boolean;\n}\n/**\n * HTML Mesh instance\n */\nexport interface HtmlMeshInstance {\n entity: pc.Entity;\n texture: pc.Texture;\n material: pc.StandardMaterial;\n htmlElement: HTMLElement;\n config: HtmlMeshConfig;\n destroy: () => void;\n update: () => void;\n}\n/**\n * Creates and manages HTML meshes in a PlayCanvas scene\n */\nexport class HtmlMeshManager {\n private app: pc.Application;\n private canvas: HTMLCanvasElement;\n private meshes: Map<string, HtmlMeshInstance> = new Map();\n private updateHandler: (() => void) | null = null;\n private useTexElement2D: boolean = false;\n constructor(app: pc.Application) {\n this.app = app;\n this.canvas = app.graphicsDevice.canvas as HTMLCanvasElement;\n // Apply runtime patch to PlayCanvas for HTML-in-Canvas support\n // This ensures _isHTMLElementInterface exists even if the bundled build is used\n patchPlayCanvasForHtmlMesh();\n // Check if texElement2D is supported\n const device = app.graphicsDevice as GraphicsDeviceWithHtmlSupport;\n this.useTexElement2D = device.supportsTexElement2D === true;\n console.log(`[HtmlMeshManager] texElement2D support: ${this.useTexElement2D}`);\n }\n /**\n * Create an HTML mesh from config\n */\n createMesh(config: HtmlMeshConfig): HtmlMeshInstance {\n const width = config.width || 512;\n const height = config.height || 512;\n // Create HTML element container\n const htmlElement = this.createHtmlElement(config, width, height);\n // Create texture\n const texture = this.createTexture(htmlElement, width, height, config);\n // Create material\n const material = this.createMaterial(texture, config);\n // Create entity with plane mesh\n const entity = this.createEntity(config, material, width, height);\n // Setup update logic if animated\n const instance: HtmlMeshInstance = {\n entity,\n texture,\n material,\n htmlElement,\n config,\n destroy: () => this.destroyMesh(config.id),\n update: () => this.updateMeshTexture(config.id)\n };\n this.meshes.set(config.id, instance);\n // Setup animation updates if needed\n if (config.animated && !this.updateHandler) {\n this.startUpdateLoop();\n }\n return instance;\n }\n /**\n * Create the HTML element for the mesh\n */\n private createHtmlElement(config: HtmlMeshConfig, width: number, height: number): HTMLElement {\n const container = document.createElement('div');\n container.id = `html-mesh-${config.id}`;\n container.style.width = `${width}px`;\n container.style.height = `${height}px`;\n container.style.position = 'absolute';\n container.style.top = '0';\n container.style.left = '0';\n container.style.pointerEvents = 'none';\n container.style.zIndex = '-1';\n container.style.overflow = 'hidden';\n // Add custom CSS if provided\n if (config.css) {\n const style = document.createElement('style');\n style.textContent = config.css;\n container.appendChild(style);\n }\n // Set HTML content\n container.innerHTML += config.html;\n // For texElement2D, the element must be a child of the canvas\n if (this.useTexElement2D) {\n // Enable layoutsubtree for HTML-in-Canvas support\n this.canvas.setAttribute('layoutsubtree', '');\n this.canvas.setAttribute('data-layoutsubtree', '');\n this.canvas.appendChild(container);\n }\n else {\n // For fallback, we can keep it hidden in the body\n container.style.visibility = 'hidden';\n document.body.appendChild(container);\n }\n return container;\n }\n /**\n * Create texture from HTML element\n */\n private createTexture(htmlElement: HTMLElement, width: number, height: number, config: HtmlMeshConfig): pc.Texture {\n const texture = new pc.Texture(this.app.graphicsDevice, {\n width,\n height,\n format: pc.PIXELFORMAT_RGBA8,\n mipmaps: false,\n minFilter: pc.FILTER_LINEAR,\n magFilter: pc.FILTER_LINEAR,\n addressU: pc.ADDRESS_CLAMP_TO_EDGE,\n addressV: pc.ADDRESS_CLAMP_TO_EDGE,\n name: `htmlMesh-${config.id}`\n });\n // Set the HTML element as the texture source\n if (this.useTexElement2D) {\n try {\n (texture as TextureWithHTMLElementSource).setSource(htmlElement);\n console.log(`[HtmlMeshManager] Using texElement2D for mesh ${config.id}`);\n }\n catch (error) {\n console.warn(`[HtmlMeshManager] texElement2D failed, falling back to canvas: ${error}`);\n this.renderToCanvas(texture, htmlElement, width, height);\n }\n }\n else {\n this.renderToCanvas(texture, htmlElement, width, height);\n }\n return texture;\n }\n /**\n * Fallback: Render HTML to canvas and use as texture source\n */\n private renderToCanvas(texture: pc.Texture, htmlElement: HTMLElement, width: number, height: number): void {\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext('2d', { willReadFrequently: true })!;\n // Create an SVG foreignObject containing the HTML\n const svg = `\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\">\n <foreignObject width=\"100%\" height=\"100%\">\n <div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"width:${width}px;height:${height}px;\">\n ${htmlElement.innerHTML}\n </div>\n </foreignObject>\n </svg>\n `;\n const img = new Image();\n const blob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' });\n const url = URL.createObjectURL(blob);\n img.onload = () => {\n ctx.drawImage(img, 0, 0);\n URL.revokeObjectURL(url);\n texture.setSource(canvas);\n };\n img.onerror = () => {\n // Fallback: just draw a colored rectangle with text\n ctx.fillStyle = '#333';\n ctx.fillRect(0, 0, width, height);\n ctx.fillStyle = '#fff';\n ctx.font = '20px Arial';\n ctx.textAlign = 'center';\n ctx.fillText('HTML Mesh', width / 2, height / 2);\n URL.revokeObjectURL(url);\n texture.setSource(canvas);\n };\n img.src = url;\n }\n /**\n * Create material for the mesh\n */\n private createMaterial(texture: pc.Texture, config: HtmlMeshConfig): pc.StandardMaterial {\n const material = new pc.StandardMaterial();\n material.diffuseMap = texture;\n material.emissiveMap = texture;\n material.emissive = new pc.Color(0.5, 0.5, 0.5);\n material.opacity = config.opacity ?? 1;\n material.blendType = config.opacity !== undefined && config.opacity < 1 ? pc.BLEND_NORMAL : pc.BLEND_NONE;\n material.cull = config.doubleSided ? pc.CULLFACE_NONE : pc.CULLFACE_BACK;\n material.update();\n return material;\n }\n /**\n * Create entity with plane mesh\n */\n private createEntity(config: HtmlMeshConfig, material: pc.StandardMaterial, width: number, height: number): pc.Entity {\n const entity = new pc.Entity(`htmlMesh-${config.id}`);\n // Calculate aspect ratio for the plane\n const aspect = width / height;\n const baseSize = 1;\n entity.addComponent('render', {\n type: 'plane',\n material,\n castShadows: config.castShadows ?? false,\n receiveShadows: config.receiveShadows ?? false\n });\n // Set transform\n entity.setPosition(config.position.x, config.position.y, config.position.z);\n const rotation = config.rotation || { x: 0, y: 0, z: 0 };\n entity.setEulerAngles(rotation.x, rotation.y, rotation.z);\n const scale = config.scale || { x: 1, y: 1, z: 1 };\n entity.setLocalScale(scale.x * aspect, 1, scale.y);\n this.app.root.addChild(entity);\n // Setup billboard if requested\n if (config.billboard) {\n this.app.on('update', () => {\n if (!entity.enabled)\n return;\n const camera = this.app.root.findComponent('camera')?.entity;\n if (camera) {\n entity.lookAt(camera.getPosition());\n }\n });\n }\n return entity;\n }\n /**\n * Update a mesh's texture\n */\n updateMeshTexture(id: string): void {\n const instance = this.meshes.get(id);\n if (!instance)\n return;\n if (this.useTexElement2D) {\n // For texElement2D, just call upload to refresh\n instance.texture.upload();\n }\n else {\n // For canvas fallback, re-render\n this.renderToCanvas(instance.texture, instance.htmlElement, instance.config.width || 512, instance.config.height || 512);\n }\n }\n /**\n * Start the update loop for animated meshes\n */\n private startUpdateLoop(): void {\n let lastUpdate: {\n [id: string]: number;\n } = {};\n this.updateHandler = () => {\n const now = performance.now();\n this.meshes.forEach((instance, id) => {\n if (!instance.config.animated)\n return;\n const rate = instance.config.updateRate || 100;\n const last = lastUpdate[id] || 0;\n if (now - last >= rate) {\n this.updateMeshTexture(id);\n lastUpdate[id] = now;\n }\n });\n };\n this.app.on('update', this.updateHandler);\n }\n /**\n * Destroy a mesh\n */\n destroyMesh(id: string): void {\n const instance = this.meshes.get(id);\n if (!instance)\n return;\n // Remove entity\n instance.entity.destroy();\n // Destroy texture\n instance.texture.destroy();\n // Remove HTML element\n instance.htmlElement.remove();\n this.meshes.delete(id);\n // Stop update loop if no animated meshes left\n const hasAnimated = Array.from(this.meshes.values()).some(m => m.config.animated);\n if (!hasAnimated && this.updateHandler) {\n this.app.off('update', this.updateHandler);\n this.updateHandler = null;\n }\n }\n /**\n * Get a mesh instance by ID\n */\n getMesh(id: string): HtmlMeshInstance | undefined {\n return this.meshes.get(id);\n }\n /**\n * Update visibility and billboard state based on progress\n * Called from the main viewer to sync with scroll/waypoint progress\n */\n updateVisibility(scrollPercent: number, waypointIndex: number): void {\n this.meshes.forEach((instance) => {\n const config = instance.config;\n // Update visibility based on range\n if (config.visibilityRange) {\n const range = config.visibilityRange;\n let visible = true;\n if (range.type === 'percentage') {\n visible = scrollPercent >= range.start && scrollPercent <= range.end;\n }\n else if (range.type === 'waypoint') {\n visible = waypointIndex >= range.start && waypointIndex <= range.end;\n }\n instance.entity.enabled = visible;\n }\n // Update billboard state based on range\n if (config.billboard && config.billboardRange) {\n const range = config.billboardRange;\n let billboardActive = false;\n if (range.type === 'percentage') {\n billboardActive = scrollPercent >= range.start && scrollPercent <= range.end;\n }\n else if (range.type === 'waypoint') {\n billboardActive = waypointIndex >= range.start && waypointIndex <= range.end;\n }\n // Store billboard active state for the update loop\n (instance.entity as HtmlMeshEntity)._billboardActive = billboardActive;\n }\n else if (config.billboard) {\n // Billboard always active if no range\n (instance.entity as HtmlMeshEntity)._billboardActive = true;\n }\n });\n }\n /**\n * Get all mesh instances\n */\n getAllMeshes(): Map<string, HtmlMeshInstance> {\n return this.meshes;\n }\n /**\n * Destroy all meshes and cleanup\n */\n destroy(): void {\n this.meshes.forEach((_, id) => this.destroyMesh(id));\n if (this.updateHandler) {\n this.app.off('update', this.updateHandler);\n this.updateHandler = null;\n }\n }\n}\n/**\n * Setup HTML meshes from config array\n */\nexport function setupHtmlMeshes(app: pc.Application, htmlMeshes: HtmlMeshConfig[]): HtmlMeshManager {\n const manager = new HtmlMeshManager(app);\n for (const config of htmlMeshes) {\n manager.createMesh(config);\n }\n return manager;\n}\n","/**\n * Custom Script System for PlayCanvas\n *\n * Provides runtime execution of user-defined scripts in the viewer.\n * Adapted from the HTML export system for PlayCanvas engine.\n */\nimport * as pc from 'playcanvas';\n/**\n * API exposed to custom scripts\n */\nexport interface CustomScriptAPI {\n /** PlayCanvas Application instance */\n app: pc.Application;\n /** Camera entity */\n camera: pc.Entity;\n /** PlayCanvas namespace */\n pc: typeof pc;\n /** Canvas element */\n canvas: HTMLCanvasElement;\n /** Get current scroll/progress percentage (0-1) */\n getScrollPercentage: () => number;\n /** Get current waypoint index */\n getCurrentWaypointIndex: () => number;\n /** Get all hotspot entities */\n getHotspots: () => pc.Entity[];\n /** Get splat mesh entities */\n getSplats: () => pc.Entity[];\n /** Get HTML mesh instances */\n getHTMLMeshes: () => unknown[];\n /** Register a cleanup function to be called on script disposal */\n registerCleanup?: (fn: () => void) => void;\n}\n/**\n * Custom Script System\n *\n * Executes user-provided scripts with access to the viewer API.\n * Includes safety measures like sandboxing and cleanup tracking.\n */\nexport class CustomScriptSystem {\n private customScript: string;\n private api: CustomScriptAPI;\n private isInitialized: boolean = false;\n private scriptCleanup: (() => void)[] = [];\n private lastError: unknown = null;\n private updateCallbacks: (() => void)[] = [];\n constructor(customScript: string) {\n this.customScript = customScript;\n this.api = {} as CustomScriptAPI;\n }\n /**\n * Initialize the script system with the viewer API\n */\n initialize(api: CustomScriptAPI): void {\n this.api = {\n ...api,\n registerCleanup: (fn: () => void) => this.addCleanup(fn)\n };\n this.isInitialized = true;\n }\n /**\n * Update the script content and re-execute\n */\n updateScript(script: string): void {\n if (script === this.customScript)\n return;\n this.customScript = script;\n this.execute();\n }\n /**\n * Add a cleanup function to be called on disposal\n */\n private addCleanup(fn: () => void): void {\n if (typeof fn === 'function') {\n this.scriptCleanup.push(fn);\n }\n }\n /**\n * Sanitize script to remove problematic characters\n */\n private sanitizeScript(script: string): string {\n if (!script)\n return '';\n let s = script.normalize('NFC');\n // Remove BOM\n s = s.replace(/\\uFEFF/g, '');\n // Replace non-breaking space with regular space\n s = s.replace(/\\u00A0/g, ' ');\n // Replace unicode line/paragraph separators with newlines\n s = s.replace(/[\\u2028\\u2029]/g, '\\n');\n // Remove zero-width characters\n s = s.replace(/[\\u200B-\\u200D\\u2060]/g, '');\n // Normalize curly quotes\n s = s.replace(/[\\u2018\\u2019\\u201B]/g, \"'\").replace(/[\\u201C\\u201D\\u201E]/g, '\"');\n return s;\n }\n /**\n * Preprocess script with safety wrapping\n */\n private preprocessScript(script: string): string {\n if (!script)\n return '';\n let processed = this.sanitizeScript(script).trim().replace(/\\r\\n/g, '\\n');\n // Warn about common typo\n if (/console\\/log\\s*\\(/.test(processed)) {\n console.warn(\"[Custom Script] Detected 'console/log(...)'. Did you mean 'console.log(...)'?\");\n processed = processed.replace(/console\\/log\\s*\\(/g, 'console.log(');\n }\n // Wrap in try-catch for safety\n processed = \"try {\\n\" + processed + \"\\n} catch (error) {\\n console.error('[Custom Script] Runtime error:', error);\\n}\";\n return processed;\n }\n /**\n * Execute the custom script\n */\n execute(): void {\n if (!this.isInitialized || !this.customScript) {\n return;\n }\n // Prevent extremely large scripts\n if (this.customScript.length > 200000) {\n console.warn('[Custom Script] Script too large, aborting execution.');\n return;\n }\n this.cleanup();\n const processedScript = this.preprocessScript(this.customScript);\n try {\n const app = this.api.app;\n let cancelled = false;\n // Watchdog to prevent runaway update callbacks\n const watchdog = () => {\n if (cancelled)\n return;\n if (this.updateCallbacks.length > 200) {\n console.warn('[Custom Script] Too many update callbacks; further callbacks ignored.');\n cancelled = true;\n }\n else {\n requestAnimationFrame(watchdog);\n }\n };\n requestAnimationFrame(watchdog);\n // Wrapped app with tracked registerUpdate (PlayCanvas equivalent of registerBeforeRender)\n const wrappedApp = Object.create(app);\n wrappedApp.registerUpdate = (callback: () => void) => {\n if (typeof callback !== 'function' || cancelled)\n return;\n const safeCallback = () => {\n try {\n callback();\n }\n catch (err) {\n console.error('[Custom Script] Error in update callback:', err);\n }\n };\n this.updateCallbacks.push(safeCallback);\n app.on('update', safeCallback);\n this.addCleanup(() => {\n app.off('update', safeCallback);\n const idx = this.updateCallbacks.indexOf(safeCallback);\n if (idx !== -1)\n this.updateCallbacks.splice(idx, 1);\n });\n };\n // Also support BabylonJS-style API for compatibility\n wrappedApp.registerBeforeRender = wrappedApp.registerUpdate;\n // Build limited API passed to user script\n const { camera, pc: pcNamespace, canvas, getScrollPercentage, getCurrentWaypointIndex, getHotspots, getSplats, getHTMLMeshes } = this.api;\n // Build the function body\n const body = \"'use strict';\\n\" + processedScript;\n // Create the function with sandboxed parameters\n // eslint-disable-next-line no-new-func\n const func = new Function('app', 'camera', 'pc', 'canvas', 'getScrollPercentage', 'getCurrentWaypointIndex', 'getHotspots', 'getSplats', 'getHTMLMeshes', 'registerCleanup', 'registerUpdate', 'exports', 'module', 'require', 'globalThis', 'window', 'document', 'self', body);\n // Provide restricted globals (prevent direct window access by shadowing)\n const exports: Record<string, unknown> = {};\n const module: {\n exports: Record<string, unknown>;\n } = { exports };\n const fakeRequire = () => { throw new Error('require not available in custom script'); };\n const blocked = new Proxy({}, { get: () => undefined, set: () => false });\n const possibleReturn = func(wrappedApp, camera, pcNamespace, canvas, getScrollPercentage, getCurrentWaypointIndex, getHotspots, getSplats, getHTMLMeshes, (fn: () => void) => this.addCleanup(fn), wrappedApp.registerUpdate.bind(wrappedApp), exports, module, fakeRequire, blocked, undefined, undefined, undefined);\n // Check for returned cleanup function\n const cleanupCandidate = possibleReturn || module.exports || exports.default || exports.cleanup;\n if (typeof cleanupCandidate === 'function') {\n this.addCleanup(cleanupCandidate);\n }\n console.log('[Custom Script] Executed successfully');\n }\n catch (error) {\n this.lastError = error;\n console.error('[Custom Script] Execution error:', error);\n }\n }\n /**\n * Run all cleanup functions\n */\n cleanup(): void {\n this.scriptCleanup.forEach(fn => {\n try {\n fn();\n }\n catch (error) {\n console.error('[Custom Script] Cleanup error:', error);\n }\n });\n this.scriptCleanup = [];\n this.updateCallbacks = [];\n }\n /**\n * Get the last error that occurred during execution\n */\n getLastError(): unknown {\n return this.lastError;\n }\n /**\n * Dispose of the script system and cleanup\n */\n dispose(): void {\n this.cleanup();\n this.isInitialized = false;\n }\n}\n/**\n * Setup custom script system with the viewer\n */\nexport function setupCustomScript(app: pc.Application, camera: pc.Entity, canvas: HTMLCanvasElement, customScript: string, getScrollPercentage: () => number, getCurrentWaypointIndex: () => number, getHotspots: () => pc.Entity[], getSplats: () => pc.Entity[], getHTMLMeshes: () => unknown[]): CustomScriptSystem | null {\n if (!customScript || customScript.trim() === '') {\n console.log('[Custom Script] No custom script provided');\n return null;\n }\n console.log('[Custom Script] Initializing custom script system...');\n console.log('[Custom Script] Script length:', customScript.length);\n const scriptSystem = new CustomScriptSystem(customScript);\n scriptSystem.initialize({\n app,\n camera,\n pc,\n canvas,\n getScrollPercentage,\n getCurrentWaypointIndex,\n getHotspots,\n getSplats,\n getHTMLMeshes\n });\n scriptSystem.execute();\n return scriptSystem;\n}\n","/**\n * FrameSequencePlayer - 4DGS Frame-by-Frame Playback\n *\n * Handles loading and playing back sequences of PLY/SOG splat files\n * for volumetric video (4D Gaussian Splatting) playback.\n */\nimport * as pc from 'playcanvas';\nexport interface FrameSequenceConfig {\n frameUrls: string[]; // Array of PLY/SOG URLs\n fps?: number; // Playback FPS (default: 24)\n loop?: boolean; // Loop playback (default: true)\n preloadCount?: number; // Frames to preload ahead (default: 5)\n autoplay?: boolean; // Start playing immediately (default: false)\n}\nexport interface FrameSequencePlayerOptions {\n onFrameChange?: (frame: number, total: number) => void;\n onLoadProgress?: (loaded: number, total: number) => void;\n onError?: (error: string) => void;\n}\ntype FramePlayerEventCallback = (...args: unknown[]) => void;\nexport class FrameSequencePlayer {\n private app: pc.Application;\n private frameUrls: string[];\n private frameAssets: Map<number, pc.Asset> = new Map();\n private entities: [\n pc.Entity,\n pc.Entity\n ];\n private activeEntityIndex: number = 0;\n private currentFrame: number = 0;\n private fps: number;\n private isPlaying: boolean = false;\n private loop: boolean;\n private preloadCount: number;\n private lastFrameTime: number = 0;\n private frameInterval: number;\n private loadingFrames: Set<number> = new Set();\n private destroyed: boolean = false;\n private isDisplaying: boolean = false;\n private listeners: Map<string, Set<FramePlayerEventCallback>> = new Map();\n constructor(app: pc.Application, config: FrameSequenceConfig, private options: FrameSequencePlayerOptions = {}) {\n this.app = app;\n this.frameUrls = config.frameUrls;\n this.fps = config.fps || 24;\n this.loop = config.loop !== false;\n this.preloadCount = config.preloadCount || 5;\n this.frameInterval = 1000 / this.fps;\n // Create double-buffer entities\n this.entities = [\n this.createSplatEntity('frameEntityA'),\n this.createSplatEntity('frameEntityB'),\n ];\n // Hide both initially\n this.entities[0].enabled = false;\n this.entities[1].enabled = false;\n // Start preloading\n this.preloadInitialFrames();\n // Auto-play if configured\n if (config.autoplay) {\n // Wait for first frame to load before playing\n this.preloadFrame(0).then(() => {\n if (!this.destroyed) {\n this.play();\n }\n });\n }\n }\n private createSplatEntity(name: string): pc.Entity {\n const entity = new pc.Entity(name);\n entity.addComponent('gsplat', {});\n this.app.root.addChild(entity);\n return entity;\n }\n /**\n * Preload initial frames for smooth playback start\n */\n private async preloadInitialFrames(): Promise<void> {\n const framesToLoad = Math.min(this.preloadCount, this.frameUrls.length);\n const loadPromises: Promise<pc.Asset | null>[] = [];\n for (let i = 0; i < framesToLoad; i++) {\n loadPromises.push(this.preloadFrame(i));\n }\n await Promise.all(loadPromises);\n this.options.onLoadProgress?.(framesToLoad, this.frameUrls.length);\n }\n /**\n * Preload a specific frame\n */\n async preloadFrame(index: number): Promise<pc.Asset | null> {\n if (this.destroyed)\n return null;\n if (index < 0 || index >= this.frameUrls.length)\n return null;\n if (this.frameAssets.has(index))\n return this.frameAssets.get(index)!;\n if (this.loadingFrames.has(index))\n return null;\n this.loadingFrames.add(index);\n return new Promise((resolve) => {\n const url = this.frameUrls[index];\n const asset = new pc.Asset(`frame_${index}`, 'gsplat', { url });\n asset.on('load', () => {\n if (!this.destroyed) {\n this.frameAssets.set(index, asset);\n this.loadingFrames.delete(index);\n }\n resolve(asset);\n });\n asset.on('error', (err: string) => {\n console.error(`Failed to load frame ${index}:`, err);\n this.loadingFrames.delete(index);\n this.options.onError?.(`Failed to load frame ${index}: ${err}`);\n resolve(null);\n });\n this.app.assets.add(asset);\n this.app.assets.load(asset);\n });\n }\n /**\n * Unload a frame to free memory\n */\n unloadFrame(index: number): void {\n const asset = this.frameAssets.get(index);\n if (asset) {\n this.app.assets.remove(asset);\n asset.unload();\n this.frameAssets.delete(index);\n }\n }\n /**\n * Update the preload window based on current frame\n */\n private updatePreloadWindow(): void {\n // Preload upcoming frames\n for (let i = 1; i <= this.preloadCount; i++) {\n const frameIndex = this.loop\n ? (this.currentFrame + i) % this.frameUrls.length\n : this.currentFrame + i;\n if (frameIndex < this.frameUrls.length) {\n this.preloadFrame(frameIndex);\n }\n }\n // Unload old frames (keep 2 behind current)\n const keepBehind = 2;\n for (const [index] of this.frameAssets) {\n const distance = this.currentFrame - index;\n if (distance > keepBehind && distance < this.frameUrls.length - this.preloadCount) {\n this.unloadFrame(index);\n }\n }\n }\n /**\n * Display a specific frame using double-buffering\n */\n private async displayFrame(index: number): Promise<void> {\n if (this.destroyed || this.isDisplaying)\n return;\n this.isDisplaying = true;\n try {\n let asset: pc.Asset | null | undefined = this.frameAssets.get(index);\n // If frame isn't loaded yet, wait for it\n if (!asset) {\n asset = await this.preloadFrame(index);\n if (!asset || this.destroyed)\n return;\n }\n const nextEntityIdx = (this.activeEntityIndex + 1) % 2;\n const currentEntity = this.entities[this.activeEntityIndex];\n const nextEntity = this.entities[nextEntityIdx];\n // Set new asset on inactive entity\n const gsplat = nextEntity.gsplat;\n if (gsplat) {\n gsplat.asset = asset;\n }\n // Swap visibility\n nextEntity.enabled = true;\n currentEntity.enabled = false;\n this.activeEntityIndex = nextEntityIdx;\n this.currentFrame = index;\n // Emit frame change event\n this.emit('frameChange', index, this.frameUrls.length);\n this.options.onFrameChange?.(index, this.frameUrls.length);\n // Update preload window\n this.updatePreloadWindow();\n } finally {\n this.isDisplaying = false;\n }\n }\n /**\n * Main update loop - called every frame\n */\n update(dt: number): void {\n if (!this.isPlaying || this.destroyed)\n return;\n const now = performance.now();\n const elapsed = now - this.lastFrameTime;\n if (elapsed >= this.frameInterval) {\n this.lastFrameTime = now - (elapsed % this.frameInterval);\n let nextFrame = this.currentFrame + 1;\n if (nextFrame >= this.frameUrls.length) {\n if (this.loop) {\n nextFrame = 0;\n }\n else {\n this.pause();\n this.emit('complete');\n return;\n }\n }\n this.displayFrame(nextFrame);\n }\n }\n // ============================================================================\n // Public Playback API\n // ============================================================================\n play(): void {\n if (this.isPlaying || this.destroyed)\n return;\n this.isPlaying = true;\n this.lastFrameTime = performance.now();\n // Show current frame if not already visible\n if (!this.entities[this.activeEntityIndex].enabled) {\n this.displayFrame(this.currentFrame);\n }\n this.emit('play');\n }\n pause(): void {\n this.isPlaying = false;\n this.emit('pause');\n }\n stop(): void {\n this.isPlaying = false;\n this.currentFrame = 0;\n this.displayFrame(0);\n this.emit('stop');\n }\n setFrame(index: number): void {\n if (index < 0)\n index = 0;\n if (index >= this.frameUrls.length)\n index = this.frameUrls.length - 1;\n this.displayFrame(index);\n }\n nextFrame(): void {\n let next = this.currentFrame + 1;\n if (next >= this.frameUrls.length) {\n next = this.loop ? 0 : this.frameUrls.length - 1;\n }\n this.setFrame(next);\n }\n previousFrame(): void {\n let prev = this.currentFrame - 1;\n if (prev < 0) {\n prev = this.loop ? this.frameUrls.length - 1 : 0;\n }\n this.setFrame(prev);\n }\n // ============================================================================\n // Getters/Setters\n // ============================================================================\n getCurrentFrame(): number {\n return this.currentFrame;\n }\n getTotalFrames(): number {\n return this.frameUrls.length;\n }\n getProgress(): number {\n return this.frameUrls.length > 1\n ? this.currentFrame / (this.frameUrls.length - 1)\n : 0;\n }\n setProgress(progress: number): void {\n const frame = Math.round(progress * (this.frameUrls.length - 1));\n this.setFrame(frame);\n }\n getFps(): number {\n return this.fps;\n }\n setFps(fps: number): void {\n this.fps = fps;\n this.frameInterval = 1000 / fps;\n }\n getIsPlaying(): boolean {\n return this.isPlaying;\n }\n setLoop(loop: boolean): void {\n this.loop = loop;\n }\n getLoop(): boolean {\n return this.loop;\n }\n // ============================================================================\n // Transform methods (apply to both entities)\n // ============================================================================\n setPosition(x: number, y: number, z: number): void {\n this.entities[0].setPosition(x, y, z);\n this.entities[1].setPosition(x, y, z);\n }\n setRotation(x: number, y: number, z: number): void {\n this.entities[0].setEulerAngles(x, y, z);\n this.entities[1].setEulerAngles(x, y, z);\n }\n setScale(x: number, y: number, z: number): void {\n this.entities[0].setLocalScale(x, y, z);\n this.entities[1].setLocalScale(x, y, z);\n }\n // ============================================================================\n // Event Emitter\n // ============================================================================\n on(event: string, callback: FramePlayerEventCallback): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(callback);\n }\n off(event: string, callback: FramePlayerEventCallback): void {\n this.listeners.get(event)?.delete(callback);\n }\n private emit(event: string, ...args: unknown[]): void {\n this.listeners.get(event)?.forEach(cb => cb(...args));\n }\n // ============================================================================\n // Cleanup\n // ============================================================================\n destroy(): void {\n this.destroyed = true;\n this.isPlaying = false;\n // Unload all assets\n for (const [index] of this.frameAssets) {\n this.unloadFrame(index);\n }\n // Remove entities\n this.entities[0].destroy();\n this.entities[1].destroy();\n this.listeners.clear();\n }\n}\n","/**\n * Dynamic Viewer for StorySplat\n *\n * Creates an embedded PlayCanvas viewer from scene data.\n * Uses the same core logic as HTML export for 100% parity.\n */\nimport * as pc from 'playcanvas';\nimport { transformSceneToExportProps, exportPropsToViewerConfig } from '../transformers/sceneToConfig';\nimport type { SceneData, ViewerInstance, EditorViewerInstance, ViewerOptions, ViewerEvent, HotspotData, LightConfig, ParticleConfig, CustomMeshConfig, WaypointData, PortalData, CollisionMeshConfig, AudioEmitterConfig, ButtonLabels } from '../types';\nimport { createUIElements, connectUIToViewer, injectStyles, hidePreloader, updatePreloaderProgress, showHotspotPopup, setJoystickVisible, updateJoystickPosition, updateLookZoneState, createLazyLoadUI, updateWaypointListActive, setupWaypointListClickHandlers, showErrorPopup, getButtonLabel, DEFAULT_BUTTON_LABELS, updateFpsCounter, type UIElements } from './viewerUI';\nimport { CameraControls } from './CameraControls';\nimport { CharacterController } from './CharacterController';\nimport { getGsplatRevealRadialClass, getRevealPreset, type RevealPreset } from '../effects';\nimport { AnimatedGifTexture, isGifUrl } from './AnimatedGifHelper';\nimport { setupHtmlMeshes, type HtmlMeshConfig, type HtmlMeshManager } from './HtmlMeshHelper';\nimport { setupCustomScript, CustomScriptSystem } from './CustomScriptSystem';\nimport { FrameSequencePlayer } from './FrameSequencePlayer';\ninterface WindowWithLegacyAudio extends Window {\n opera?: string;\n webkitAudioContext?: typeof AudioContext;\n}\ninterface AudioSlotWithStoredVolume extends pc.SoundSlot {\n _storedVolume?: number;\n}\ninterface ViewerRuntimeApp extends pc.Application {\n __htmlMeshManager?: HtmlMeshManager;\n __customScriptSystem?: CustomScriptSystem;\n}\ninterface ViewerRuntimeEntity extends pc.Entity {\n modelEntity?: pc.Entity;\n _billboardActive?: boolean;\n _billboardOriginalRotation?: pc.Vec3;\n visibilityRange?: {\n type: 'percentage' | 'waypoint';\n start: number;\n end: number;\n };\n opacityConfig?: {\n mode: string;\n value: number;\n };\n shouldBeVisible?: boolean;\n hiddenUntilTextureLoaded?: boolean;\n textureLoaded?: boolean;\n targetOpacity?: number;\n hotspotMaterial?: pc.StandardMaterial;\n portalMaterial?: pc.StandardMaterial;\n videoElement?: HTMLVideoElement;\n alphaVideoElement?: HTMLVideoElement | null;\n mediaTriggerMode?: string;\n proximityDistance?: number;\n isVideoPlaying?: boolean;\n videoSpatialAudio?: {\n audioCtx?: AudioContext;\n } | null;\n audioElements?: {\n audio: HTMLAudioElement;\n audioCtx?: AudioContext;\n source?: MediaElementAudioSourceNode;\n panner?: PannerNode;\n updateAudioPosition?: () => void;\n };\n hotspotData?: HotspotData;\n portalData?: PortalData;\n meshClickHandler?: (event: MouseEvent) => void;\n meshHoverHandler?: (event: MouseEvent) => void;\n meshLeaveHandler?: () => void;\n _collisionMeshId?: string;\n wasInProximity?: boolean;\n proximityTriggered?: boolean;\n gifCanvas?: HTMLCanvasElement;\n gifTexture?: pc.Texture;\n gifAnimationFrame?: number;\n}\ninterface UIElementsWithPortalPopup extends UIElements {\n portalPopup?: HTMLElement;\n showHotspotPopup?: (hotspot: HotspotData) => void;\n}\ninterface LegacyVec3 {\n x?: number;\n y?: number;\n z?: number;\n _x?: number;\n _y?: number;\n _z?: number;\n}\ninterface LegacyQuat extends LegacyVec3 {\n w?: number;\n _w?: number;\n}\ninterface LegacyColor {\n r?: number;\n g?: number;\n b?: number;\n a?: number;\n}\ninterface LegacyParticleConfig extends ParticleConfig {\n emitterPosition?: LegacyVec3;\n gravity?: LegacyVec3;\n color1?: LegacyColor;\n color2?: LegacyColor;\n colorDead?: LegacyColor;\n direction1?: LegacyVec3;\n direction2?: LegacyVec3;\n minLifeTime?: number;\n maxLifeTime?: number;\n emitterType?: string;\n emitBoxMin?: LegacyVec3;\n emitBoxMax?: LegacyVec3;\n blendMode?: string;\n minEmitPower?: number;\n maxEmitPower?: number;\n minSize?: number;\n maxSize?: number;\n minAngularSpeed?: number;\n maxAngularSpeed?: number;\n angularSpeed?: number;\n minInitialRotation?: number;\n maxInitialRotation?: number;\n minScaleX?: number;\n maxScaleX?: number;\n minScaleY?: number;\n maxScaleY?: number;\n emitRate?: number;\n depthWrite?: boolean;\n softParticles?: number;\n halfLambert?: boolean;\n alignToMotion?: boolean;\n stretch?: number;\n autoPlay?: boolean;\n orientation?: number;\n localSpace?: boolean;\n colorGradients?: Array<{\n gradient: number;\n color: LegacyColor;\n }>;\n}\ninterface LegacyWaypointAudioConfig {\n loop?: boolean;\n volume?: number;\n spatialSound?: boolean;\n distanceModel?: string;\n maxDistance?: number;\n refDistance?: number;\n rolloffFactor?: number;\n autoplay?: boolean;\n stopOnExit?: boolean;\n url?: string;\n}\ninterface SceneWithToneMapping extends pc.Scene {\n toneMapping?: number;\n}\n/** Typed reveal effect script with specific properties from GsplatRevealRadial */\ninterface RevealEffectScript extends pc.ScriptType {\n center: pc.Vec3;\n speed: number;\n acceleration: number;\n delay: number;\n oscillationIntensity: number;\n dotTint: pc.Color;\n waveTint: pc.Color;\n endRadius: number;\n}\ntype LightConfigLike = Omit<LightConfig, 'position' | 'rotation' | 'direction'> & {\n position?: LegacyVec3;\n rotation?: LegacyVec3;\n direction?: LegacyVec3;\n enabled?: boolean;\n angle?: number;\n exponent?: number;\n innerAngle?: number;\n outerAngle?: number;\n shadowBias?: number;\n normalOffsetBias?: number;\n};\ntype WaypointConfigLike = Omit<WaypointData, 'position' | 'interactions'> & {\n position?: LegacyVec3;\n interactions?: Array<{\n id?: string;\n type?: string;\n data?: LegacyWaypointAudioConfig;\n }>;\n};\ntype CustomMeshConfigLike = Omit<CustomMeshConfig, 'position' | 'rotation' | 'scale' | 'interaction'> & {\n position?: LegacyVec3;\n rotation?: LegacyVec3;\n scale?: LegacyVec3;\n enabled?: boolean;\n interaction?: (CustomMeshConfig['interaction'] & {\n popupContent?: string;\n }) | undefined;\n};\ntype HotspotConfigLike = Omit<HotspotData, 'position' | 'rotation' | 'scale' | 'id' | 'type' | 'data' | 'activationMode'> & {\n position?: LegacyVec3;\n rotation?: LegacyVec3;\n scale?: number | LegacyVec3;\n id?: string;\n type?: string;\n data?: unknown;\n activationMode?: string;\n enabled?: boolean;\n videoSpatialAudio?: boolean;\n videoDistanceModel?: DistanceModelType;\n videoRefDistance?: number;\n videoMaxDistance?: number;\n videoRolloffFactor?: number;\n};\ntype PortalConfigLike = Omit<PortalData, 'position' | 'rotation' | 'scale' | 'id' | 'type' | 'targetSceneId' | 'activationMode'> & {\n position?: LegacyVec3;\n rotation?: LegacyVec3;\n scale?: number | LegacyVec3;\n id?: string;\n type?: string;\n targetSceneId?: string;\n activationMode?: string;\n enabled?: boolean;\n useLighting?: boolean;\n proximityDistance?: number;\n confirmNavigation?: boolean;\n};\ntype GSplatComponentWithRuntime = Omit<pc.GSplatComponent, '_instance'> & {\n unified?: boolean;\n instance?: pc.GSplatInstance;\n _instance?: pc.GSplatInstance;\n lodDistances?: number[];\n};\ntype ScriptComponentWithCreate = Omit<pc.ScriptComponent, 'create'> & {\n create?: (scriptType: typeof pc.ScriptType | string) => pc.ScriptType;\n};\ninterface GsplatHandlerWithOctreeParser {\n _octreeParser?: {\n load: (urlObj: {\n load: string;\n original?: string;\n }, callback: (err: unknown, resource: unknown) => void) => void;\n };\n}\ninterface GsplatHandlerWithParsers {\n parsers?: {\n octree?: {\n load: (urlObj: {\n load: string;\n original?: string;\n }, callback: (err: unknown, resource: unknown) => void) => void;\n };\n };\n}\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype EventCallback = (...args: any[]) => void;\n// Event emitter for viewer events\nclass EventEmitter {\n private listeners: Map<string, Set<EventCallback>> = new Map();\n on(event: string, callback: EventCallback): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(callback);\n }\n off(event: string, callback: EventCallback): void {\n this.listeners.get(event)?.delete(callback);\n }\n emit(event: string, ...args: unknown[]): void {\n this.listeners.get(event)?.forEach(cb => cb(...args));\n }\n}\n// Mobile detection utility\nfunction isMobileDevice(): boolean {\n // Only check user agent - don't use maxTouchPoints as many laptops have touch\n const userAgent = navigator.userAgent || navigator.vendor || (window as WindowWithLegacyAudio).opera || '';\n return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);\n}\n// LOD (Level of Detail) presets for different device capabilities\n// Based on official PlayCanvas GSplat LOD streaming example\nconst LOD_PRESETS = {\n 'desktop-max': {\n range: [0, 5] as [\n number,\n number\n ],\n lodDistances: [15, 30, 80, 250, 300]\n },\n 'desktop': {\n range: [0, 2] as [\n number,\n number\n ],\n lodDistances: [15, 30, 80, 250, 300]\n },\n 'mobile-max': {\n range: [1, 2] as [\n number,\n number\n ],\n lodDistances: [15, 30, 80, 250, 300]\n },\n 'mobile': {\n range: [2, 5] as [\n number,\n number\n ],\n lodDistances: [15, 30, 80, 250, 300]\n }\n} as const;\ntype LodPresetName = keyof typeof LOD_PRESETS;\n// Helper to detect if URL points to LOD streaming format\nfunction isLodStreamingFormat(url: string): boolean {\n const isLod = url.includes('lod-meta.json');\n if (isLod) {\n console.log('[SPLAT] Detected LOD streaming format (lod-meta.json)');\n }\n return isLod;\n}\n// Helper to detect SOG format\nfunction isSogFormat(url: string): boolean {\n return url.includes('.sog') || url.includes('lod-meta.json');\n}\n/**\n * Create an embedded viewer from scene data\n */\nexport function createViewer(container: HTMLElement, scene: SceneData, options: ViewerOptions = {}): ViewerInstance {\n console.log('[StorySplat Viewer] BUILD 2025-01-22-orbit-fly-v3');\n // Handle lazy loading - show thumbnail with start button before loading viewer\n if (options.lazyLoad) {\n const lazyEvents = new EventEmitter();\n let actualInstance: ViewerInstance | null = null;\n let pendingButtonLabels: Partial<ButtonLabels> | undefined;\n // Determine thumbnail URL and button text\n const thumbnailUrl = options.lazyLoadThumbnail || scene.uiOptions?.lazyLoadThumbnailUrl || scene.thumbnailUrl;\n // Priority: explicit option > scene lazyLoadButtonText > scene buttonLabels.startExperience > default\n const buttonText = options.lazyLoadButtonText\n || scene.uiOptions?.lazyLoadButtonText\n || getButtonLabel(scene.uiOptions?.buttonLabels, 'startExperience');\n const uiColor = scene.uiColor || '#4CAF50';\n console.log('[StorySplat Viewer] Lazy loading enabled, showing start button');\n // Create lazy load UI (supports image, video, and GIF thumbnails)\n createLazyLoadUI(container, {\n thumbnailUrl,\n thumbnailType: options.lazyLoadThumbnailType || scene.uiOptions?.lazyLoadThumbnailType,\n buttonText,\n uiColor,\n onStart: () => {\n console.log('[StorySplat Viewer] User clicked start, initializing viewer...');\n // Create actual viewer without lazy loading\n actualInstance = createViewer(container, scene, { ...options, lazyLoad: false });\n // Apply any pending button labels that were set before the instance was created\n if (pendingButtonLabels) {\n actualInstance.setButtonLabels(pendingButtonLabels);\n pendingButtonLabels = undefined;\n }\n // Proxy events from actual instance to lazy instance\n actualInstance.on('ready', () => lazyEvents.emit('ready'));\n actualInstance.on('error', (err) => lazyEvents.emit('error', err));\n actualInstance.on('waypointChange', (data) => lazyEvents.emit('waypointChange', data));\n actualInstance.on('playbackStart', () => lazyEvents.emit('playbackStart'));\n actualInstance.on('playbackStop', () => lazyEvents.emit('playbackStop'));\n actualInstance.on('loaded', () => lazyEvents.emit('loaded'));\n actualInstance.on('progress', (data) => lazyEvents.emit('progress', data));\n }\n });\n // Return deferred ViewerInstance - methods delegate to actual instance when available\n const deferredInstance: ViewerInstance = {\n app: null as unknown as pc.Application,\n canvas: null as unknown as HTMLCanvasElement,\n goToWaypoint: (index) => actualInstance?.goToWaypoint(index),\n nextWaypoint: () => actualInstance?.nextWaypoint(),\n prevWaypoint: () => actualInstance?.prevWaypoint(),\n getCurrentWaypointIndex: () => actualInstance?.getCurrentWaypointIndex() ?? 0,\n getWaypointCount: () => actualInstance?.getWaypointCount() ?? 0,\n setPosition: (x, y, z) => actualInstance?.setPosition(x, y, z),\n setRotation: (x, y, z) => actualInstance?.setRotation(x, y, z),\n getPosition: () => actualInstance?.getPosition() ?? { x: 0, y: 0, z: 0 },\n getRotation: () => actualInstance?.getRotation() ?? { x: 0, y: 0, z: 0 },\n play: () => actualInstance?.play(),\n pause: () => actualInstance?.pause(),\n stop: () => actualInstance?.stop(),\n isPlaying: () => actualInstance?.isPlaying() ?? false,\n // Mode\n setCameraMode: (mode) => actualInstance?.setCameraMode(mode),\n getCameraMode: () => actualInstance?.getCameraMode() ?? 'tour',\n setExploreMode: (mode) => actualInstance?.setExploreMode(mode),\n // Splat API\n goToOriginalSplat: () => actualInstance?.goToOriginalSplat(),\n goToSplat: async (url) => actualInstance?.goToSplat(url),\n getCurrentSplatUrl: () => actualInstance?.getCurrentSplatUrl() ?? '',\n isShowingOriginalSplat: () => actualInstance?.isShowingOriginalSplat() ?? true,\n getAdditionalSplats: () => actualInstance?.getAdditionalSplats() ?? [],\n // Progress\n setProgress: (progress) => actualInstance?.setProgress(progress),\n getProgress: () => actualInstance?.getProgress() ?? 0,\n // Audio\n muteAll: () => actualInstance?.muteAll(),\n unmuteAll: () => actualInstance?.unmuteAll(),\n isMuted: () => actualInstance?.isMuted() ?? false,\n // Hotspot interaction\n getHotspots: () => actualInstance?.getHotspots() ?? [],\n triggerHotspot: (id) => actualInstance?.triggerHotspot(id),\n closeHotspot: () => actualInstance?.closeHotspot(),\n destroy: () => {\n if (actualInstance) {\n actualInstance.destroy();\n }\n else {\n // Clean up lazy load UI if viewer wasn't started\n container.querySelectorAll('.storysplat-lazy-load-container, .storysplat-viewer-container').forEach(el => el.remove());\n container.classList.remove('storysplat-viewer-container');\n }\n },\n resize: () => actualInstance?.resize(),\n navigateToScene: async (sceneId) => {\n if (actualInstance) {\n return actualInstance.navigateToScene(sceneId);\n }\n },\n setButtonLabels: (labels) => {\n if (actualInstance) {\n actualInstance.setButtonLabels(labels);\n }\n else {\n pendingButtonLabels = { ...pendingButtonLabels, ...labels };\n }\n },\n on: (event, callback) => lazyEvents.on(event, callback),\n off: (event, callback) => lazyEvents.off(event, callback)\n };\n return deferredInstance;\n }\n const events = new EventEmitter();\n // Style isolation: reset inherited styles from parent page unless opted out\n if (!options.allowParentStyles) {\n const originalWidth = container.style.width;\n const originalHeight = container.style.height;\n container.style.all = 'initial';\n container.style.position = 'relative';\n container.style.display = 'block';\n container.style.width = originalWidth || '100%';\n container.style.height = originalHeight || '100%';\n container.style.overflow = 'hidden';\n container.style.fontFamily = 'system-ui, -apple-system, sans-serif';\n }\n // Set up internal error handler to show user-friendly error popup\n events.on('error', (err: Error) => {\n console.error('[StorySplat Viewer] Error event:', err.message);\n showErrorPopup(container, err.message);\n });\n const config = exportPropsToViewerConfig(transformSceneToExportProps(scene));\n console.log('[StorySplat Viewer] Creating viewer with config:', config);\n console.log('[StorySplat Viewer] Scale config:', config.scale);\n console.log('[StorySplat Viewer] Raw scene data:', { splatScale: scene.splatScale, scale: scene.scale });\n // Extract UI options from config (respecting JSON settings)\n const isEditorMode = options.editor === true;\n const showUI = isEditorMode ? (options.editorSkipUI ? false : options.showUI !== false) : (options.showUI !== false);\n const uiColor = config.uiColor || '#4CAF50';\n const uiOpts = config.uiOptions || {};\n // Resolve template type: explicit option > scene uiOptions > default minimal\n const templateType = options.template || uiOpts.uiType || 'minimal';\n // Mutable button labels for runtime updates via setButtonLabels()\n let currentButtonLabels: ButtonLabels | undefined = uiOpts.buttonLabels;\n // Map camera modes from export format to UI format\n const mapCameraMode = (mode: string) => {\n if (mode === 'first-person')\n return 'tour';\n if (mode === 'drone')\n return 'explore';\n return mode;\n };\n // Check if collision meshes are available for walk mode\n const hasCollisionMeshesForWalk = config.collisionMeshesData && config.collisionMeshesData.length > 0;\n let allowedModes = (config.allowedCameraModes || ['orbit', 'first-person', 'drone'])\n .map(mapCameraMode)\n .filter((v, i, a) => a.indexOf(v) === i); // unique\n // Add 'walk' mode if collision meshes are available and it's in the allowed modes list\n // OR automatically add it if collision meshes exist (for backwards compatibility)\n if (hasCollisionMeshesForWalk && !allowedModes.includes('walk')) {\n allowedModes.push('walk');\n }\n // Remove 'walk' if no collision meshes (can't walk without collisions)\n if (!hasCollisionMeshesForWalk && allowedModes.includes('walk')) {\n allowedModes = allowedModes.filter(m => m !== 'walk');\n }\n const defaultMode = mapCameraMode(config.defaultCameraMode || 'orbit');\n let uiElements: UIElements = {};\n // Create UI elements FIRST (to show preloader while loading)\n if (showUI) {\n uiElements = createUIElements(container, config, {\n uiColor,\n showScrollControls: config.waypoints && config.waypoints.length > 0,\n showModeToggle: allowedModes.length > 1,\n showFullscreenButton: !uiOpts.hideFullscreenButton,\n showHelpButton: !uiOpts.hideHelpButton && !uiOpts.hideInfoButton,\n showPreloader: true,\n allowedCameraModes: allowedModes,\n defaultCameraMode: defaultMode,\n buttonLabels: currentButtonLabels,\n // Whitelabeling options for preloader\n customPreloaderLogoUrl: uiOpts.customPreloaderLogoUrl,\n // Whitelabeling options for watermark\n hideWatermark: uiOpts.hideWatermark,\n watermarkText: uiOpts.watermarkText,\n watermarkLink: uiOpts.watermarkLink,\n sceneId: scene.sceneId,\n // Waypoint list dropdown\n showWaypointList: uiOpts.showWaypointList,\n // Layout template\n template: templateType,\n // Debug mode (FPS counter)\n debugMode: uiOpts.debugMode\n });\n }\n // Create canvas\n const canvas = document.createElement('canvas');\n canvas.id = 'storysplat-viewer-canvas';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n canvas.style.display = 'block';\n container.appendChild(canvas);\n // Initialize PlayCanvas with WebGL fallback chain\n // Try WebGL2 first, then WebGL1 if that fails\n let app: pc.Application;\n // Graphics device options with fallback support\n const baseGraphicsOptions = {\n antialias: false,\n alpha: false,\n powerPreference: 'high-performance' as const\n };\n try {\n // First try with default settings (WebGL2 preferred)\n app = new pc.Application(canvas, {\n graphicsDeviceOptions: baseGraphicsOptions,\n mouse: new pc.Mouse(canvas),\n touch: new pc.TouchDevice(canvas),\n keyboard: new pc.Keyboard(window)\n });\n console.log('[StorySplat Viewer] Graphics initialized successfully');\n }\n catch (webgl2Error) {\n console.warn('[StorySplat Viewer] WebGL2 initialization failed, trying WebGL1 fallback:', webgl2Error);\n try {\n // Fallback to WebGL1\n app = new pc.Application(canvas, {\n graphicsDeviceOptions: {\n ...baseGraphicsOptions,\n preferWebGl2: false\n },\n mouse: new pc.Mouse(canvas),\n touch: new pc.TouchDevice(canvas),\n keyboard: new pc.Keyboard(window)\n });\n console.log('[StorySplat Viewer] WebGL1 fallback successful');\n }\n catch (webgl1Error) {\n console.error('[StorySplat Viewer] WebGL initialization failed completely:', webgl1Error);\n // Show user-friendly error message using safe DOM methods\n const errorDiv = document.createElement('div');\n errorDiv.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;background:rgba(0,0,0,0.8);padding:20px;border-radius:10px;text-align:center;font-family:sans-serif;';\n const heading = document.createElement('h3');\n heading.style.cssText = 'margin:0 0 10px 0;';\n heading.textContent = getButtonLabel(currentButtonLabels, 'errorWebGLTitle');\n const message = document.createElement('p');\n message.style.cssText = 'margin:0;';\n message.textContent = getButtonLabel(currentButtonLabels, 'errorWebGLMessage');\n errorDiv.appendChild(heading);\n errorDiv.appendChild(message);\n container.appendChild(errorDiv);\n throw new Error('WebGL initialization failed - browser may not support WebGL');\n }\n }\n // Handle WebGL context loss\n canvas.addEventListener('webglcontextlost', (e) => {\n e.preventDefault();\n console.error('[StorySplat Viewer] WebGL context lost');\n events.emit('error', new Error('WebGL context lost'));\n }, false);\n canvas.addEventListener('webglcontextrestored', () => {\n console.log('[StorySplat Viewer] WebGL context restored');\n // PlayCanvas should handle context restoration automatically\n }, false);\n app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);\n app.setCanvasResolution(pc.RESOLUTION_AUTO);\n // Start the app first (before adding entities)\n app.start();\n console.log('[StorySplat Viewer] App started');\n // Configure GSplat LOD system for optimal performance\n // This improves rendering for all splat formats (PLY, SOG, LOD)\n const isMobile = isMobileDevice();\n const lodPresetName: LodPresetName = isMobile ? 'mobile' : 'desktop';\n const lodPreset = LOD_PRESETS[lodPresetName];\n console.log('[SPLAT] Initializing LOD system for device:', isMobile ? 'mobile' : 'desktop');\n // Scene-level GSplat settings (applies to all gsplat components)\n if (app.scene.gsplat) {\n // LOD update settings - controls how frequently LOD is recalculated\n app.scene.gsplat.lodUpdateAngle = 90; // Angle change threshold for LOD update\n app.scene.gsplat.lodBehindPenalty = 2; // Penalty multiplier for splats behind camera\n app.scene.gsplat.radialSorting = true; // Enable radial sorting for better quality\n app.scene.gsplat.lodUpdateDistance = 1; // Distance change threshold for LOD update\n app.scene.gsplat.lodUnderfillLimit = 10; // Limit for underfill detection\n // LOD range settings - controls which LOD levels are used\n app.scene.gsplat.lodRangeMin = lodPreset.range[0];\n app.scene.gsplat.lodRangeMax = lodPreset.range[1];\n // SH (Spherical Harmonics) update settings for better color accuracy\n app.scene.gsplat.colorUpdateDistance = 1;\n app.scene.gsplat.colorUpdateAngle = 4;\n app.scene.gsplat.colorUpdateDistanceLodScale = 2;\n app.scene.gsplat.colorUpdateAngleLodScale = 2;\n console.log('[SPLAT] LOD system configured:', {\n preset: lodPresetName,\n lodRangeMin: lodPreset.range[0],\n lodRangeMax: lodPreset.range[1],\n lodDistances: lodPreset.lodDistances,\n lodUpdateAngle: 90,\n lodUpdateDistance: 1,\n isMobile\n });\n }\n else {\n console.warn('[SPLAT] GSplat scene settings not available - LOD may not work optimally');\n }\n // State\n let currentWaypointIndex = 0;\n let isPlaying = false;\n let splatEntity: pc.Entity | null = null;\n let revealScript: RevealEffectScript | null = null; // Reference to reveal effect script\n let isDestroyed = false; // Flag to prevent async operations after destroy\n let frameSequencePlayer: FrameSequencePlayer | null = null; // 4DGS frame sequence player\n // SplatSwap system state\n const ORIGINAL_SPLAT_URL = \"__ORIGINAL__\";\n const additionalSplats = config.additionalSplats || [];\n const keepMeshesInMemory = config.keepMeshesInMemory ?? false;\n const initialSplatExploreMode = config.initialSplatExploreMode || 'fly'; // Default to fly (POV)\n let currentSplatUrl: string | null = null;\n let isLoadingSplat = false;\n let initialSplatLoadDone = false;\n const preloadedSplats = new Map<string, pc.Entity>(); // Map URL -> splat entity\n let lastSplatCheckProgress = -1;\n let lastSplatCheckWaypointIndex = -1;\n // Create camera (matching HTML export CONFIG values)\n const camera = new pc.Entity('camera');\n // Parse background color from config (supports hex strings like \"#1a1a1a\")\n // Default to dark gray (0.1, 0.1, 0.1) if not specified\n let clearColor = new pc.Color(0.1, 0.1, 0.1);\n if (config.backgroundColor) {\n // Parse hex color string to RGB\n const hex = config.backgroundColor.replace('#', '');\n if (hex.length === 6) {\n const r = parseInt(hex.substring(0, 2), 16) / 255;\n const g = parseInt(hex.substring(2, 4), 16) / 255;\n const b = parseInt(hex.substring(4, 6), 16) / 255;\n clearColor = new pc.Color(r, g, b);\n console.log('[StorySplat Viewer] Background color set from config:', config.backgroundColor);\n }\n }\n camera.addComponent('camera', {\n clearColor,\n fov: config.fov || 60,\n nearClip: config.nearClip || 0.1,\n farClip: config.farClip || 1000\n });\n // Add audio listener for spatial audio (required for positional sound)\n camera.addComponent('audiolistener');\n console.log('[StorySplat Viewer] Camera settings:', {\n fov: config.fov,\n nearClip: config.nearClip,\n farClip: config.farClip,\n playerHeight: config.playerHeight\n });\n // Set initial camera position - matching HTML export behavior\n // If waypoints exist, set camera DIRECTLY to first waypoint to avoid 1-frame flash\n const playerHeight = config.playerHeight || 1.6;\n if (config.waypoints && config.waypoints.length > 0) {\n const wp = config.waypoints[0];\n console.log('[StorySplat Viewer] First waypoint raw:', wp);\n // Set position SYNCHRONOUSLY to avoid camera flash\n // Convert BabylonJS (left-handed) to PlayCanvas (right-handed) - negate Z\n if (wp.position) {\n const pos = wp.position;\n camera.setPosition(pos.x, pos.y, -pos.z);\n console.log('[StorySplat Viewer] Camera position (Z negated):', { x: pos.x, y: pos.y, z: -pos.z });\n }\n else {\n camera.setPosition(0, playerHeight, 5);\n }\n // Set rotation SYNCHRONOUSLY\n if (wp.rotation) {\n const finalRot = convertWaypointRotation(wp.rotation);\n camera.setRotation(finalRot);\n console.log('[StorySplat Viewer] Camera rotation set from waypoint');\n }\n }\n else {\n // No waypoints - use default position\n camera.setPosition(0, playerHeight, 5);\n camera.lookAt(new pc.Vec3(0, 0, 0));\n console.log('[StorySplat Viewer] Camera set to default position (0, playerHeight, 5)');\n }\n app.root.addChild(camera);\n // Add directional light (matching HTML export)\n const light = new pc.Entity('light');\n light.addComponent('light', {\n type: pc.LIGHTTYPE_DIRECTIONAL,\n color: new pc.Color(1, 1, 1),\n intensity: 1,\n castShadows: false\n });\n light.setEulerAngles(45, 45, 0);\n app.root.addChild(light);\n console.log('[StorySplat Viewer] Light added');\n // Create CameraControls for explore mode (orbit + fly + focus)\n // Movement and rotation speeds tuned to match BabylonJS editor behavior\n // Damping values lowered for more responsive BabylonJS-like feel (0.75 vs previous 0.9)\n // Note: cameraRotationSensitivity is stored as BabylonJS angularSensibility (friction scale: 1000-100000, default 4000)\n // We convert it to PlayCanvas rotateSpeed by inverting: lower friction = higher sensitivity\n // BabylonJS uses much smaller speed values by default (e.g. 0.2).\n // Scale up for PlayCanvas so default scenes don't feel sluggish.\n const baseMoveSpeed = (config.cameraMovementSpeed || 1) * 5;\n const cameraControls = new CameraControls(camera, app, {\n moveSpeed: baseMoveSpeed,\n moveFastSpeed: baseMoveSpeed * 2.5,\n moveSlowSpeed: baseMoveSpeed * 0.5,\n rotateSpeed: 800 / (config.cameraRotationSensitivity || 4000),\n enableOrbit: true,\n enableFly: true,\n enablePan: true,\n invertRotation: config.invertCameraRotation,\n // Damping controls camera smoothing - hardcoded to 0.75 to match BabylonJS responsive feel\n // Lower = more responsive, higher = more smooth/floaty\n moveDamping: 0.75,\n rotateDamping: 0.75,\n zoomDamping: 0.8\n });\n // Create CharacterController for walk mode (first-person with collisions)\n let characterController: CharacterController | null = null;\n const hasCollisionMeshes = config.collisionMeshesData && config.collisionMeshesData.length > 0;\n if (hasCollisionMeshes) {\n // CharacterController for walk mode - tuned for BabylonJS parity\n // Uses same conversions as CameraControls for consistent feel\n characterController = new CharacterController(camera, app, {\n moveSpeed: baseMoveSpeed,\n sprintMultiplier: 2,\n lookSensitivity: (1 / (config.cameraRotationSensitivity || 4000)) * 10,\n playerHeight: config.playerHeight || 1.6,\n gravity: 20,\n jumpVelocity: 8,\n collisionRadius: 0.3,\n stepHeight: 0.3\n });\n // Load collision meshes asynchronously\n characterController.createCollisionMeshes(config.collisionMeshesData!).then(() => {\n console.log('[StorySplat Viewer] Collision meshes loaded for walk mode');\n // Share collision entities with CameraControls for explore mode (fly) collision\n cameraControls.setCollisionEntities(characterController!.collisionMeshEntities);\n }).catch((err) => {\n console.error('[StorySplat Viewer] Failed to load collision meshes:', err);\n });\n }\n // Track current camera mode\n let currentCameraMode = defaultMode;\n let waypointControlEnabled = true; // When false, waypoint navigation doesn't control camera\n // Per-waypoint orbit mode state (for tour mode with orbit waypoints)\n let currentWaypointOrbitEnabled = false;\n let currentWaypointOrbitTarget: pc.Vec3 | null = null;\n // Disable controls initially if in tour mode\n if (defaultMode === 'tour') {\n cameraControls.disable();\n }\n // Create picker for GSplat picking (double-click to focus)\n // Enable depth=true for getWorldPointAsync to work with splats (requires PlayCanvas 2.14+)\n const picker = new pc.Picker(app, 1, 1, true);\n // XR (VR/AR) Support\n let isInXR = false;\n let xrSessionType: 'vr' | 'ar' | null = null;\n // Setup XR if enabled in config\n function setupXR(): void {\n if (!config.includeXR)\n return;\n // Check if XR is supported\n if (!app.xr) {\n console.warn('[StorySplat Viewer] WebXR not supported in this browser');\n return;\n }\n const xr = app.xr; // Non-null reference for TypeScript\n const xrMode = config.xrMode || 'both';\n // Check VR availability\n if (xrMode === 'vr' || xrMode === 'both') {\n if (xr.isAvailable(pc.XRTYPE_VR)) {\n uiElements.vrButton?.classList.add('available');\n console.log('[StorySplat Viewer] VR is available');\n }\n // Listen for VR availability changes\n xr.on('available:' + pc.XRTYPE_VR, (available: boolean) => {\n if (available) {\n uiElements.vrButton?.classList.add('available');\n }\n else {\n uiElements.vrButton?.classList.remove('available');\n }\n });\n }\n // Check AR availability\n if (xrMode === 'ar' || xrMode === 'both') {\n if (xr.isAvailable(pc.XRTYPE_AR)) {\n uiElements.arButton?.classList.add('available');\n console.log('[StorySplat Viewer] AR is available');\n }\n // Listen for AR availability changes\n xr.on('available:' + pc.XRTYPE_AR, (available: boolean) => {\n if (available) {\n uiElements.arButton?.classList.add('available');\n }\n else {\n uiElements.arButton?.classList.remove('available');\n }\n });\n }\n // Handle XR session start\n xr.on('start', () => {\n isInXR = true;\n console.log('[StorySplat Viewer] XR session started');\n // Update button states\n if (xrSessionType === 'vr') {\n uiElements.vrButton?.classList.add('active');\n uiElements.vrButton!.textContent = getButtonLabel(currentButtonLabels, 'exitVr');\n }\n else if (xrSessionType === 'ar') {\n uiElements.arButton?.classList.add('active');\n uiElements.arButton!.textContent = getButtonLabel(currentButtonLabels, 'exitAr');\n }\n // Disable regular camera controls in XR\n cameraControls.disable();\n if (characterController) {\n characterController.disable();\n }\n events.emit('xrStart', { type: xrSessionType });\n });\n // Handle XR session end\n xr.on('end', () => {\n isInXR = false;\n console.log('[StorySplat Viewer] XR session ended');\n // Hide AR content panel when exiting XR\n hideARContent();\n // Reset button states\n uiElements.vrButton?.classList.remove('active');\n uiElements.arButton?.classList.remove('active');\n if (uiElements.vrButton)\n uiElements.vrButton.textContent = getButtonLabel(currentButtonLabels, 'vr');\n if (uiElements.arButton)\n uiElements.arButton.textContent = getButtonLabel(currentButtonLabels, 'ar');\n xrSessionType = null;\n // Re-enable camera controls based on current mode\n if (currentCameraMode === 'explore') {\n cameraControls.enable();\n }\n else if (currentCameraMode === 'walk' && characterController) {\n characterController.enable();\n }\n events.emit('xrEnd', {});\n });\n // VR button click handler\n if (uiElements.vrButton) {\n uiElements.vrButton.addEventListener('click', () => {\n if (isInXR && xrSessionType === 'vr') {\n // Exit VR\n xr.end();\n }\n else if (!isInXR && xr.isAvailable(pc.XRTYPE_VR)) {\n // Enter VR\n xrSessionType = 'vr';\n camera.camera!.startXr(pc.XRTYPE_VR, pc.XRSPACE_LOCALFLOOR, {\n callback: (err: Error | null) => {\n if (err) {\n console.error('[StorySplat Viewer] Failed to start VR:', err);\n xrSessionType = null;\n }\n }\n });\n }\n });\n }\n // AR button click handler\n if (uiElements.arButton) {\n uiElements.arButton.addEventListener('click', () => {\n if (isInXR && xrSessionType === 'ar') {\n // Exit AR\n xr.end();\n }\n else if (!isInXR && xr.isAvailable(pc.XRTYPE_AR)) {\n // Enter AR\n xrSessionType = 'ar';\n camera.camera!.startXr(pc.XRTYPE_AR, pc.XRSPACE_LOCALFLOOR, {\n callback: (err: Error | null) => {\n if (err) {\n console.error('[StorySplat Viewer] Failed to start AR:', err);\n xrSessionType = null;\n }\n }\n });\n }\n });\n }\n }\n // AR Content Display - 3D plane for showing hotspot info in XR mode\n let arContentEntity: pc.Entity | null = null;\n let arContentTexture: pc.Texture | null = null;\n let arContentVisible = false;\n // Helper for word wrapping text on canvas\n function wrapText(ctx: CanvasRenderingContext2D, text: string, x: number, y: number, maxWidth: number, lineHeight: number): number {\n const words = text.split(' ');\n let line = '';\n let currentY = y;\n for (const word of words) {\n const testLine = line + word + ' ';\n if (ctx.measureText(testLine).width > maxWidth && line) {\n ctx.fillText(line.trim(), x, currentY);\n line = word + ' ';\n currentY += lineHeight;\n }\n else {\n line = testLine;\n }\n }\n ctx.fillText(line.trim(), x, currentY);\n return currentY + lineHeight;\n }\n function showARContent(hotspot: HotspotData): void {\n if (!isInXR)\n return;\n if (!arContentEntity) {\n // Create plane entity for AR content display\n arContentEntity = new pc.Entity('arContentPlane');\n arContentEntity.addComponent('render', { type: 'plane' });\n arContentEntity.setLocalScale(0.8, 1, 0.45); // 16:9 aspect ratio roughly\n app.root.addChild(arContentEntity);\n }\n // Create dynamic texture with hotspot info\n const canvasWidth = 512;\n const canvasHeight = 288;\n const canvas = document.createElement('canvas');\n canvas.width = canvasWidth;\n canvas.height = canvasHeight;\n const ctx = canvas.getContext('2d')!;\n // Render background with rounded corners effect\n const bgColor = hotspot.backgroundColor || 'rgba(20, 20, 20, 0.95)';\n ctx.fillStyle = bgColor;\n ctx.fillRect(0, 0, canvasWidth, canvasHeight);\n // Add subtle border\n ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';\n ctx.lineWidth = 2;\n ctx.strokeRect(1, 1, canvasWidth - 2, canvasHeight - 2);\n const textColor = hotspot.textColor || '#ffffff';\n let currentY = 35;\n // Render title\n if (hotspot.title) {\n ctx.fillStyle = textColor;\n ctx.font = 'bold 28px Arial, sans-serif';\n ctx.fillText(hotspot.title, 20, currentY);\n currentY += 40;\n }\n // Render information with word wrap\n if (hotspot.information) {\n ctx.fillStyle = textColor;\n ctx.font = '18px Arial, sans-serif';\n currentY = wrapText(ctx, hotspot.information, 20, currentY, canvasWidth - 40, 24);\n }\n // Render \"Tap to close\" hint at bottom\n ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';\n ctx.font = '14px Arial, sans-serif';\n ctx.textAlign = 'center';\n ctx.fillText('Tap to close', canvasWidth / 2, canvasHeight - 15);\n ctx.textAlign = 'left';\n // Create/update texture\n if (!arContentTexture) {\n arContentTexture = new pc.Texture(app.graphicsDevice, {\n width: canvasWidth,\n height: canvasHeight,\n format: pc.PIXELFORMAT_RGBA8,\n mipmaps: false,\n minFilter: pc.FILTER_LINEAR,\n magFilter: pc.FILTER_LINEAR\n });\n }\n arContentTexture.setSource(canvas);\n // Create/update material\n const material = new pc.StandardMaterial();\n material.diffuse = new pc.Color(0, 0, 0);\n material.diffuseMap = arContentTexture;\n material.emissive = new pc.Color(1, 1, 1);\n material.emissiveMap = arContentTexture;\n material.useLighting = false;\n material.blendType = pc.BLEND_NORMAL;\n material.cull = pc.CULLFACE_NONE;\n material.depthWrite = true;\n material.update();\n if (arContentEntity.render && arContentEntity.render.meshInstances[0]) {\n arContentEntity.render.meshInstances[0].material = material;\n }\n arContentEntity.enabled = true;\n arContentVisible = true;\n console.log('[StorySplat Viewer] AR content displayed for hotspot:', hotspot.title);\n }\n function hideARContent(): void {\n if (arContentEntity) {\n arContentEntity.enabled = false;\n arContentVisible = false;\n }\n }\n function updateARContentPosition(): void {\n if (arContentEntity?.enabled && camera) {\n // Position the AR content panel 1.5 meters in front of camera, slightly below eye level\n const forward = camera.forward.clone();\n const cameraPos = camera.getPosition();\n const panelPos = cameraPos.clone().add(forward.mulScalar(1.5));\n panelPos.y -= 0.1; // Slightly below eye level for comfortable viewing\n arContentEntity.setPosition(panelPos);\n // Make the plane face the camera\n arContentEntity.lookAt(cameraPos);\n // Rotate 90 degrees on X axis since plane faces up by default\n arContentEntity.rotateLocal(90, 0, 0);\n }\n }\n // FPS counter update throttle\n let fpsUpdateTimer = 0;\n // Add update loop for CameraControls, CharacterController, and proximity checks\n app.on('update', (dt: number) => {\n // Update FPS counter (throttled to every 500ms)\n if (uiElements.fpsCounter) {\n fpsUpdateTimer += dt;\n if (fpsUpdateTimer >= 0.5) {\n fpsUpdateTimer = 0;\n updateFpsCounter(uiElements, 1 / dt);\n }\n }\n // Update either CameraControls or CharacterController based on mode\n if (currentCameraMode === 'walk' && characterController) {\n characterController.update(dt);\n }\n else {\n cameraControls.update(dt);\n }\n // Check proximity triggers for video hotspots (needed for explore/walk mode)\n if (!waypointControlEnabled) {\n updateProximityTriggers();\n }\n // Update waypoint audio based on proximity (for spatial audio)\n updateWaypointAudioProximity();\n // Update audio emitter proximity (for spatial audio)\n updateAudioEmitterProximity();\n // Check waypoint trigger distances for interaction execution\n checkWaypointTriggerDistance();\n // Update AR content panel position in XR mode\n if (isInXR && arContentVisible) {\n updateARContentPosition();\n }\n });\n // Listen to CameraControls joystick events for UI updates\n app.on('joystick:left', (bx: number, by: number, sx: number, sy: number) => {\n if (bx < 0 || by < 0) {\n // Joystick released\n updateJoystickPosition(uiElements, false, 0, 0, 60);\n }\n else {\n // Joystick active - calculate offset from base to stick\n const dx = sx - bx;\n const dy = sy - by;\n updateJoystickPosition(uiElements, true, dx, dy, 60);\n }\n });\n // Listen to right joystick (look zone) events for UI updates\n app.on('joystick:right', (bx: number, by: number, sx: number, sy: number) => {\n if (bx < 0 || by < 0) {\n // Look control released\n updateLookZoneState(uiElements, false);\n }\n else {\n // Look control active\n updateLookZoneState(uiElements, true);\n }\n });\n // Orbit/Fly text buttons in scroll controls (both desktop and mobile)\n const exploreBtns = uiElements.scrollControls?.querySelectorAll('.storysplat-explore-btn');\n const updateExploreBtnState = (activeMode: 'orbit' | 'fly') => {\n exploreBtns?.forEach(btn => {\n const mode = btn.getAttribute('data-explore-mode');\n btn.classList.toggle('selected', mode === activeMode);\n });\n };\n exploreBtns?.forEach(btn => {\n btn.addEventListener('click', () => {\n if (currentCameraMode !== 'explore')\n return;\n const mode = btn.getAttribute('data-explore-mode') as 'orbit' | 'fly';\n if (mode === 'orbit') {\n cameraControls.setMode('orbit');\n updateExploreBtnState('orbit');\n if (isMobile)\n setJoystickVisible(uiElements, false);\n }\n else {\n cameraControls.setMode('fly');\n updateExploreBtnState('fly');\n if (isMobile)\n setJoystickVisible(uiElements, true);\n }\n });\n });\n // Listen for CameraControls internal mode changes (e.g., after focus completes)\n app.on('cameracontrols:modechange', (mode: string) => {\n if (mode === 'orbit' || mode === 'fly') {\n updateExploreBtnState(mode as 'orbit' | 'fly');\n // Update joystick visibility on mobile\n if (isMobile && currentCameraMode === 'explore') {\n setJoystickVisible(uiElements, mode === 'fly');\n }\n }\n });\n // Proximity check function (called every frame in explore mode)\n function updateProximityTriggers(): void {\n const cameraPos = camera.getPosition();\n hotspotEntities.forEach((entity) => {\n const hotspot = entity.hotspotData;\n if (!hotspot)\n return;\n const triggerMode = entity.mediaTriggerMode || 'click';\n if (triggerMode !== 'proximity')\n return;\n const hotspotPos = entity.getPosition();\n const distance = cameraPos.distance(hotspotPos);\n const proximityDistance = entity.proximityDistance || 5;\n const isInProximity = distance <= proximityDistance;\n const wasInProximity = entity.wasInProximity || false;\n if (isInProximity && !wasInProximity) {\n // Entering proximity\n if (hotspot.type === 'video' && entity.videoElement) {\n playVideoHotspot(entity, hotspot);\n }\n else if (hotspot.type === 'gif') {\n entity.enabled = true;\n }\n else if ((hotspot.type === 'sphere' || hotspot.type === 'image') && entity.audioElements) {\n // Play audio for sphere/image hotspots with audio\n const audioElements = entity.audioElements as HotspotAudioElements;\n const audio = audioElements.audio;\n if (audio && audio.paused) {\n // Resume AudioContext on proximity trigger (may need user interaction first)\n if (audioElements.audioCtx && audioElements.audioCtx.state === 'suspended') {\n audioElements.audioCtx.resume();\n }\n audio.play().catch(err => console.error('[Audio] Hotspot audio play failed (proximity):', err));\n console.log('[Audio] Hotspot audio started (proximity):', hotspot.title);\n }\n }\n entity.wasInProximity = true;\n }\n else if (!isInProximity && wasInProximity) {\n // Leaving proximity\n if (hotspot.type === 'video' && entity.videoElement) {\n pauseVideoHotspot(entity);\n }\n else if (hotspot.type === 'gif') {\n entity.enabled = false;\n }\n else if ((hotspot.type === 'sphere' || hotspot.type === 'image') && entity.audioElements) {\n // Stop audio for sphere/image hotspots with audio\n const audioElements = entity.audioElements as HotspotAudioElements;\n const audio = audioElements.audio;\n if (audio && !audio.paused) {\n audio.pause();\n console.log('[Audio] Hotspot audio stopped (proximity):', hotspot.title);\n }\n }\n entity.wasInProximity = false;\n }\n });\n }\n // Mode switching function (Tour, Explore, or Walk)\n function setCameraMode(mode: string): void {\n // Disable previous mode's controllers\n if (currentCameraMode === 'walk' && characterController) {\n characterController.disable();\n }\n else if (currentCameraMode === 'explore') {\n cameraControls.disable();\n }\n currentCameraMode = mode;\n console.log('[StorySplat Viewer] Switching to mode:', mode);\n const isMobile = isMobileDevice();\n if (mode === 'walk' && characterController) {\n // Walk mode: Enable CharacterController (first-person with collisions)\n waypointControlEnabled = false;\n // Disable CameraControls\n cameraControls.disable();\n // Enable CharacterController\n characterController.enable();\n // Hide joystick (CharacterController uses keyboard/mouse)\n setJoystickVisible(uiElements, false);\n }\n else if (mode === 'explore') {\n // Explore mode: Enable CameraControls (orbit + fly + pan)\n // Disable CharacterController if active\n if (characterController) {\n characterController.disable();\n }\n // CRITICAL: Reset user rotation offsets and disable waypoint control\n userYawOffset = 0;\n userPitchOffset = 0;\n isUserDragging = false;\n waypointControlEnabled = false;\n // IMPORTANT: Call disable() first to clear any accumulated input from tour mode dragging\n // The CameraControls input sources accumulate mouse/touch input even when disabled,\n // and this would cause an offset when we enable and the update loop reads that input\n cameraControls.disable(); // Clears input buffers\n cameraControls.enable();\n cameraControls.enableOrbit = true;\n cameraControls.enableFly = true;\n cameraControls.enablePan = true;\n // Determine the explore sub-mode to apply\n const exploreDefault = initialSplatExploreMode || 'fly';\n // Start explore mode from the current camera position (both orbit and fly)\n if (targetCameraPosition && targetCameraRotation) {\n // Use syncFromPose to directly set the camera from the waypoint target values\n // This bypasses the camera entity's current state which may have user rotation applied\n cameraControls.syncFromPose(targetCameraPosition, targetCameraRotation);\n console.log('[StorySplat Viewer] Synced camera from waypoint pose for explore mode');\n }\n else {\n cameraControls.syncFromCamera();\n }\n // Try to find a better focus point using the picker (async improvement)\n const findBetterFocusPoint = async () => {\n try {\n const pickerScale = 0.25;\n picker.resize(Math.floor(canvasEl.clientWidth * pickerScale), Math.floor(canvasEl.clientHeight * pickerScale));\n const worldLayer = app.scene.layers.getLayerByName('World');\n if (worldLayer) {\n picker.prepare(camera.camera!, app.scene, [worldLayer]);\n const centerX = Math.floor(canvasEl.clientWidth * 0.5 * pickerScale);\n const centerY = Math.floor(canvasEl.clientHeight * 0.5 * pickerScale);\n const worldPoint = await picker.getWorldPointAsync(centerX, centerY);\n if (worldPoint) {\n const cameraPos = camera.getPosition();\n const distance = cameraPos.distance(worldPoint);\n if (distance > 0.5 && distance < 500) {\n cameraControls.syncFromCamera(worldPoint);\n console.log('[StorySplat Viewer] Updated focus target at distance:', distance.toFixed(2));\n }\n }\n }\n }\n catch (err) {\n // Picking failed, keep default\n }\n };\n findBetterFocusPoint();\n // Apply the explore sub-mode (already determined above)\n cameraControls.setMode(exploreDefault);\n updateExploreBtnState(exploreDefault as 'orbit' | 'fly');\n if (isMobile) {\n setJoystickVisible(uiElements, exploreDefault === 'fly');\n }\n }\n else {\n // Tour mode - disable camera controls, enable waypoint control\n cameraControls.disable();\n if (characterController) {\n characterController.disable();\n }\n waypointControlEnabled = true;\n // Hide joystick in tour mode\n setJoystickVisible(uiElements, false);\n }\n events.emit('modeChange', { mode });\n }\n // Update preloader progress helper (defined early so loadSplat can use it)\n const updateProgress = (progress: number, text?: string) => {\n if (uiElements.preloader) {\n updatePreloaderProgress(uiElements.preloader, progress, text, getButtonLabel(currentButtonLabels, 'loading'));\n }\n events.emit('progress', { progress, text });\n };\n // Helper to check if URL is hosted on StorySplat servers (Firebase Storage)\n // Only count bandwidth for files we host, not self-hosted files\n function isStorySplatHostedUrl(url: string): boolean {\n const storySplatHosts = [\n 'firebasestorage.googleapis.com/v0/b/story-splat',\n 'storage.googleapis.com/story-splat',\n 'storysplat.com',\n 'discover.storysplat.com'\n ];\n return storySplatHosts.some(host => url.includes(host));\n }\n // Load splat\n async function loadSplat(): Promise<void> {\n // Track bandwidth used during loading (only for StorySplat-hosted files)\n let bandwidthUsed = 0;\n let loadedUrl = ''; // Track which URL was successfully loaded\n // Build URL list with priority: LOD streaming > SOG > PLY/Other formats\n // This ensures backward compatibility with all formats while preferring optimal ones\n const urls: string[] = [];\n console.log('[SPLAT] Building URL priority list...');\n console.log('[SPLAT] Available URLs:', {\n lodMetaUrl: config.lodMetaUrl || '(none)',\n sogUrl: config.sogUrl || '(none)',\n splatUrl: config.splatUrl || '(none)',\n fallbackUrls: config.fallbackUrls?.length || 0\n });\n // Highest priority: LOD streaming format (lod-meta.json) if available\n // NOTE: lod-meta.json should have RELATIVE paths for chunk meta.json files (e.g., \"0_0/meta.json\")\n // PlayCanvas resolves these relative to the lod-meta.json base URL\n // Only the chunk meta.json files should have full URLs for their texture references\n if (config.lodMetaUrl) {\n urls.push(config.lodMetaUrl);\n console.log('[SPLAT] ✓ LOD streaming URL added (highest priority):', config.lodMetaUrl);\n }\n // Second priority: SOG compressed format\n if (config.sogUrl) {\n urls.push(config.sogUrl);\n console.log('[SPLAT] ✓ SOG URL added (second priority):', config.sogUrl);\n }\n // Third priority: Original format (PLY, SPLAT, etc.)\n if (config.splatUrl) {\n urls.push(config.splatUrl);\n console.log('[SPLAT] ✓ Original splat URL added (third priority):', config.splatUrl);\n }\n // Fallback URLs for additional format support\n if (config.fallbackUrls) {\n urls.push(...config.fallbackUrls);\n console.log('[SPLAT] ✓ Fallback URLs added:', config.fallbackUrls);\n }\n console.log('[SPLAT] Final URL priority order:', urls);\n console.log('[SPLAT] Will try URLs in order: LOD streaming > SOG > PLY/Other');\n updateProgress(0.3, getButtonLabel(currentButtonLabels, 'loading'));\n for (const url of urls) {\n if (!url)\n continue;\n try {\n // Determine asset type based on extension\n const ext = url.split('.').pop()?.toLowerCase() || 'splat';\n const assetType = 'gsplat'; // PlayCanvas uses 'gsplat' for all gaussian splat formats\n // Check format type for logging\n const isLodFormat = url.includes('lod-meta.json');\n const urlIsSogFormat = url.includes('.sog') || isLodFormat;\n console.log('[SPLAT] Attempting to load URL:', url);\n console.log('[SPLAT] Format detection:', {\n extension: ext,\n isLodStreaming: isLodFormat,\n isSogFormat: urlIsSogFormat,\n assetType: assetType\n });\n const asset = new pc.Asset('splat-' + Date.now(), assetType, { url });\n // Note: lod-meta.json filenames are rewritten to absolute CDN URLs during upload,\n // so PlayCanvas's GSplatOctree uses them as-is (path.isRelativePath returns false).\n // No mapUrl workaround is needed.\n // Track asset loading progress\n asset.on('progress', (received: number, total: number) => {\n if (total > 0) {\n // Track bandwidth: use received bytes (actual data transferred)\n // Only track if this is a StorySplat-hosted URL\n if (isStorySplatHostedUrl(url)) {\n bandwidthUsed = received;\n }\n // Map progress from 0.3 to 0.9 (leaving 0.9-1.0 for post-load setup)\n const loadProgress = 0.3 + (received / total) * 0.6;\n const percent = Math.round((received / total) * 100);\n if (percent % 25 === 0 || percent === 100) { // Log at 25%, 50%, 75%, 100%\n console.log(`[SPLAT] Loading progress: ${percent}% (${(received / 1024 / 1024).toFixed(2)}MB / ${(total / 1024 / 1024).toFixed(2)}MB)`);\n }\n updateProgress(loadProgress, `${getButtonLabel(currentButtonLabels, 'loading')} ${percent}%`);\n }\n });\n // Track which URL we're attempting to load\n loadedUrl = url;\n await new Promise<void>((resolve, reject) => {\n let hasRejected = false;\n // Catch unhandled promise rejections during loading (e.g., SOG v1 parse errors)\n // Listen for SOG formats which may have deprecated format issues\n const unhandledRejectionHandler = urlIsSogFormat ? (event: PromiseRejectionEvent) => {\n if (hasRejected)\n return;\n const errorMsg = event.reason?.message || String(event.reason);\n // Check if this is a SOG parsing error (deprecated v1 format)\n if (errorMsg.includes('shape') || errorMsg.includes('upgradeMeta')) {\n console.warn('[SPLAT] ⚠️ Parse error detected (likely deprecated v1 format):', errorMsg);\n hasRejected = true;\n event.preventDefault(); // Prevent console error\n // Emit a deprecation warning event for UI to display\n events.emit('warning', {\n type: 'deprecated_format',\n message: 'This scene uses an outdated SOG format. Please re-upload your scene with the latest tools for better performance.',\n details: 'SOG v1 format is deprecated. Use splat-transform v2+ to convert your PLY files.',\n url: url\n });\n console.log('[SPLAT] Will try fallback URL if available...');\n reject(new Error(`SOG parse error: ${errorMsg}`));\n }\n } : null;\n if (urlIsSogFormat && unhandledRejectionHandler) {\n console.log('[SPLAT] Registered error handler for SOG format parsing');\n window.addEventListener('unhandledrejection', unhandledRejectionHandler);\n }\n // Cleanup handler when done\n const cleanup = () => {\n if (urlIsSogFormat && unhandledRejectionHandler) {\n window.removeEventListener('unhandledrejection', unhandledRejectionHandler);\n }\n };\n // Use asset.ready() like HTML export - this waits for asset AND resources to be ready\n asset.ready(() => {\n // Check if viewer was destroyed while loading\n if (isDestroyed) {\n console.log('[SPLAT] Ignoring load - viewer was destroyed during loading');\n cleanup();\n reject(new Error('Viewer destroyed'));\n return;\n }\n console.log('[SPLAT] ✓ Asset loaded and resources ready');\n try {\n splatEntity = new pc.Entity('splat');\n splatEntity.addComponent('gsplat', {\n asset: asset,\n unified: true // Enable unified sorting with other transparent objects (hotspots)\n });\n // Set LOD distances for LOD streaming format\n // This enables distance-based quality switching when using lod-meta.json\n const gs = splatEntity.gsplat as GSplatComponentWithRuntime | undefined;\n if (gs && isLodStreamingFormat(url)) {\n gs.lodDistances = [...lodPreset.lodDistances];\n console.log('[SPLAT] ✓ LOD streaming enabled for this splat');\n console.log('[SPLAT] LOD distances configured:', {\n distances: lodPreset.lodDistances,\n description: 'Quality levels switch at these camera distances',\n preset: lodPresetName\n });\n }\n else if (urlIsSogFormat) {\n console.log('[SPLAT] ✓ Single SOG file loaded (no LOD streaming)');\n }\n else {\n console.log('[SPLAT] Standard format loaded (PLY/SPLAT)');\n }\n // Apply scale - match BabylonJS behavior exactly\n // In BabylonJS: invertYScale negates Y, and when invertX !== invertY, Z is also negated to maintain handedness\n const scale = config.scale || { x: 1, y: 1, z: 1 };\n const invertX = config.invertXScale || false;\n const invertY = config.invertYScale || false;\n const finalScale = {\n x: invertX ? -scale.x : scale.x,\n y: invertY ? -scale.y : scale.y,\n z: (invertX !== invertY) ? -scale.z : scale.z // Invert Z when only one of X or Y is inverted\n };\n splatEntity.setLocalScale(finalScale.x, finalScale.y, finalScale.z);\n // Apply position (negate Z for Babylon->PlayCanvas coordinate conversion)\n const pos = config.position || [0, 0, 0];\n splatEntity.setPosition(pos[0], pos[1], -pos[2]);\n // Apply rotation - convert from BabylonJS (left-handed) to PlayCanvas (right-handed)\n // ALWAYS apply 180° X base correction for splat orientation, then add user rotation\n const rot = config.rotation || [0, 0, 0];\n const baseRotX = 180; // Base correction to flip splat for PlayCanvas coordinate system\n const finalRotDeg: number[] = [\n baseRotX + rot[0] * (180 / Math.PI),\n rot[1] * (180 / Math.PI),\n -rot[2] * (180 / Math.PI)\n ];\n splatEntity.setEulerAngles(finalRotDeg[0], finalRotDeg[1], finalRotDeg[2]);\n console.log('[StorySplat Viewer] Splat transform applied:', {\n scale: finalScale,\n position: [pos[0], pos[1], -pos[2]],\n rotation: finalRotDeg,\n userRotation: rot,\n invertXScale: invertX,\n invertYScale: invertY\n });\n app.root.addChild(splatEntity);\n console.log('[StorySplat Viewer] Splat entity added to scene');\n // Set alphaClip for GSplat picking support (required for pc.Picker to work with splats)\n // Keep this LOW to reduce \"holes\" in the splat depth buffer, which can cause\n // hotspots/portals behind geometry to show through.\n if (splatEntity.gsplat?.material) {\n splatEntity.gsplat.material.setParameter('alphaClip', 0.01);\n console.log('[StorySplat Viewer] GSplat alphaClip set for picking support');\n }\n // Apply reveal effect if configured (but start disabled - will enable after preloader hides)\n const revealPresetName = (options.revealEffect || scene.revealEffect || 'medium') as RevealPreset;\n const revealConfig = getRevealPreset(revealPresetName);\n if (revealConfig) {\n // Add script component for reveal effect\n splatEntity.addComponent('script');\n // Create the reveal effect (use getter to get class after app is initialized)\n const GsplatRevealRadialClass = getGsplatRevealRadialClass();\n revealScript = ((splatEntity.script as ScriptComponentWithCreate | undefined)?.create?.(GsplatRevealRadialClass) as RevealEffectScript | undefined) ?? null;\n if (revealScript) {\n // Start disabled - will be enabled after preloader hides\n revealScript.enabled = false;\n // Apply preset configuration\n revealScript.center.set(0, 0, 0);\n revealScript.speed = revealConfig.speed;\n revealScript.acceleration = revealConfig.acceleration;\n revealScript.delay = revealConfig.delay;\n revealScript.oscillationIntensity = revealConfig.oscillationIntensity;\n revealScript.dotTint.set(revealConfig.dotTint.r, revealConfig.dotTint.g, revealConfig.dotTint.b);\n revealScript.waveTint.set(revealConfig.waveTint.r, revealConfig.waveTint.g, revealConfig.waveTint.b);\n revealScript.endRadius = revealConfig.endRadius;\n console.log('[StorySplat Viewer] Reveal effect configured (waiting to start):', revealPresetName);\n }\n }\n else {\n console.log('[StorySplat Viewer] Reveal effect disabled');\n }\n // Wait a brief moment to let async texture loading errors surface\n // before resolving (SOG v1 parse errors happen asynchronously)\n setTimeout(() => {\n if (hasRejected)\n return; // Already rejected by unhandled rejection handler\n cleanup();\n resolve();\n }, 100);\n }\n catch (syncError) {\n console.error('[StorySplat Viewer] Error during gsplat setup:', syncError);\n cleanup();\n reject(syncError);\n }\n });\n asset.on('error', (err: unknown) => {\n // Enhanced error logging to help diagnose SOG/PLY loading issues\n const errObj = err as Record<string, unknown> | null;\n console.error('[SPLAT] ✗ Asset load error:', {\n url,\n assetType,\n isSogFormat: urlIsSogFormat,\n isLodFormat: isLodFormat,\n error: err,\n message: errObj?.message || 'Unknown error',\n status: errObj?.status || errObj?.statusCode || 'N/A'\n });\n cleanup();\n reject(err);\n });\n app.assets.add(asset);\n // For LOD streaming URLs, bypass PlayCanvas's parser routing.\n // PlayCanvas selects the parser via path.getBasename(url) === 'lod-meta.json',\n // but Firebase/CDN URLs use %2F encoding which makes getBasename() return the\n // entire encoded path instead of just 'lod-meta.json'. This causes PlayCanvas\n // to route to SogParser instead of GSplatOctreeParser, which crashes.\n // Fix: directly invoke the octree parser for LOD URLs.\n if (isLodFormat) {\n const handler = app.loader.getHandler('gsplat') as GsplatHandlerWithParsers | null;\n const octreeParser = handler?.parsers?.octree;\n if (octreeParser) {\n console.log('[SPLAT] Using direct octree parser for LOD URL (bypassing basename routing)');\n const urlObj = { load: url, original: url };\n (octreeParser as { load: (urlObj: { load: string; original?: string }, callback: (err: unknown, resource: unknown) => void, asset?: pc.Asset) => void }).load(urlObj, (err: unknown, resource: unknown) => {\n if (err) {\n console.error('[SPLAT] Octree parser error:', err);\n asset.fire('error', err);\n return;\n }\n // Wire up the resource the same way PlayCanvas's asset registry does\n asset.resource = resource as object;\n asset.loaded = true;\n asset.fire('load', asset);\n }, asset);\n }\n else {\n console.warn('[SPLAT] Could not access octree parser, falling back to standard load');\n app.assets.load(asset);\n }\n }\n else {\n app.assets.load(asset);\n }\n });\n const isHostedOnStorySplat = isStorySplatHostedUrl(loadedUrl);\n // Only report bandwidth for StorySplat-hosted files\n events.emit('loaded', {\n bandwidthUsed: isHostedOnStorySplat ? bandwidthUsed : 0,\n isStorySplatHosted: isHostedOnStorySplat\n });\n console.log('[SPLAT] ✓✓✓ SPLAT LOADED SUCCESSFULLY ✓✓✓');\n console.log('[SPLAT] Load summary:', {\n url: loadedUrl,\n format: isLodFormat ? 'LOD streaming (lod-meta.json)' : urlIsSogFormat ? 'SOG (compressed)' : 'Standard (PLY/SPLAT)',\n lodEnabled: isLodFormat,\n lodPreset: isLodFormat ? lodPresetName : 'N/A',\n bandwidthUsed: `${(bandwidthUsed / 1024 / 1024).toFixed(2)}MB`,\n isStorySplatHosted: isHostedOnStorySplat,\n bandwidthCounted: isHostedOnStorySplat ? 'Yes' : 'No (self-hosted)'\n });\n return;\n }\n catch (err) {\n console.warn('[SPLAT] ✗ Failed to load URL, trying next fallback:', url);\n console.warn('[SPLAT] Error:', err);\n }\n }\n console.error('[SPLAT] ✗✗✗ FAILED TO LOAD SPLAT FROM ANY URL ✗✗✗');\n console.error('[SPLAT] Tried URLs:', urls);\n events.emit('error', new Error('Failed to load splat from any URL'));\n }\n // Load 4DGS frame sequence\n async function loadFrameSequence(): Promise<void> {\n if (!scene.frameSequence || !scene.frameSequence.frameUrls || scene.frameSequence.frameUrls.length === 0) {\n throw new Error('No frame sequence URLs provided');\n }\n console.log('[StorySplat Viewer] Loading 4DGS frame sequence:', scene.frameSequence.frameUrls.length, 'frames');\n updateProgress(0.3, 'Loading 4DGS frames...');\n frameSequencePlayer = new FrameSequencePlayer(app, {\n frameUrls: scene.frameSequence.frameUrls,\n fps: scene.frameSequence.fps || 24,\n loop: scene.frameSequence.loop !== false,\n preloadCount: scene.frameSequence.preloadCount || 5,\n autoplay: scene.frameSequence.autoplay || false,\n }, {\n onFrameChange: (frame, total) => {\n events.emit('frameChange', frame, total);\n },\n onLoadProgress: (loaded, total) => {\n const progress = 0.3 + (loaded / total) * 0.6;\n updateProgress(progress, `Loading frames... ${loaded}/${total}`);\n },\n onError: (error) => {\n console.error('[StorySplat Viewer] Frame sequence error:', error);\n events.emit('error', new Error(error));\n }\n });\n // Forward 'complete' event as 'frameComplete' for the public API\n frameSequencePlayer.on('complete', () => {\n events.emit('frameComplete');\n });\n // Hook into app update loop\n app.on('update', (dt: number) => {\n if (frameSequencePlayer && !isDestroyed) {\n frameSequencePlayer.update(dt);\n }\n });\n console.log('[StorySplat Viewer] 4DGS frame sequence player initialized');\n // Frame sequences don't track bandwidth yet - emit 0 for now\n events.emit('loaded', { bandwidthUsed: 0, isStorySplatHosted: false });\n }\n // Helper to convert waypoint rotation to PlayCanvas quaternion\n // Matching POC waypointNavigation.ts exactly - NO yFlip\n function convertWaypointRotation(rot: LegacyQuat): pc.Quat {\n if ('_w' in rot || 'w' in rot) {\n // Quaternion format - BabylonJS to PlayCanvas conversion\n // Negate X and Y for handedness conversion (matching POC exactly)\n const qx = rot._x ?? rot.x ?? 0;\n const qy = rot._y ?? rot.y ?? 0;\n const qz = rot._z ?? rot.z ?? 0;\n const qw = rot._w ?? rot.w ?? 1;\n return new pc.Quat(-qx, -qy, qz, qw);\n }\n else if ('x' in rot && 'y' in rot && 'z' in rot) {\n // Euler angles\n const result = new pc.Quat();\n result.setFromEulerAngles(rot.x || 0, rot.y || 0, rot.z || 0);\n return result;\n }\n else {\n return new pc.Quat();\n }\n }\n // =========================================================\n // SPLATSWAP SYSTEM\n // Handles swapping between multiple splat files during navigation\n // =========================================================\n /**\n * Hide a preloaded splat (Option 1: keep in memory)\n */\n function hideSplat(entity: pc.Entity): void {\n entity.enabled = false;\n console.log('[SplatSwap] Splat hidden');\n }\n /**\n * Dispose of a preloaded splat (Option 2: free memory)\n */\n function disposeSplat(url: string): void {\n const entity = preloadedSplats.get(url);\n if (entity) {\n entity.destroy();\n preloadedSplats.delete(url);\n console.log('[SplatSwap] Splat disposed:', url);\n }\n }\n /**\n * Show a preloaded splat\n */\n function showSplat(url: string): boolean {\n const entity = preloadedSplats.get(url);\n if (!entity)\n return false;\n entity.enabled = true;\n currentSplatUrl = url;\n console.log('[SplatSwap] Splat shown:', url);\n return true;\n }\n /**\n * Preload a splat in the background without displaying it\n */\n async function preloadSplat(url: string): Promise<void> {\n if (preloadedSplats.has(url))\n return;\n if (url === currentSplatUrl)\n return;\n if (isDestroyed)\n return;\n console.log('[SplatSwap] Preloading splat:', url);\n try {\n const asset = new pc.Asset('splat-preload-' + Date.now(), 'gsplat', { url });\n await new Promise<void>((resolve, reject) => {\n asset.ready(() => {\n if (isDestroyed) {\n reject(new Error('Viewer destroyed'));\n return;\n }\n const entity = new pc.Entity('splat-preload');\n entity.addComponent('gsplat', { asset, unified: true });\n // Apply scale/position/rotation matching main splat\n const scale = config.scale || { x: 1, y: 1, z: 1 };\n const invertX = config.invertXScale || false;\n const invertY = config.invertYScale || false;\n const finalScale = {\n x: invertX ? -scale.x : scale.x,\n y: invertY ? -scale.y : scale.y,\n z: (invertX !== invertY) ? -scale.z : scale.z\n };\n entity.setLocalScale(finalScale.x, finalScale.y, finalScale.z);\n const pos = config.position || [0, 0, 0];\n entity.setPosition(pos[0], pos[1], -pos[2]);\n const rot = config.rotation || [0, 0, 0];\n const baseRotX = 180; // Base correction for PlayCanvas coordinate system\n const finalRotDeg = [\n baseRotX + rot[0] * (180 / Math.PI),\n rot[1] * (180 / Math.PI),\n -rot[2] * (180 / Math.PI)\n ];\n entity.setEulerAngles(finalRotDeg[0], finalRotDeg[1], finalRotDeg[2]);\n // Start hidden\n entity.enabled = false;\n app.root.addChild(entity);\n preloadedSplats.set(url, entity);\n console.log('[SplatSwap] Preload complete:', url);\n resolve();\n });\n asset.on('error', (err: unknown) => {\n console.error('[SplatSwap] Preload error:', err);\n reject(err);\n });\n app.assets.add(asset);\n app.assets.load(asset);\n });\n }\n catch (error) {\n console.error('[SplatSwap] Error preloading:', url, error);\n }\n }\n /**\n * Apply the appropriate explore sub-mode for a splat\n * Only applies if currently in explore mode\n */\n function applyExploreModeForSplat(splatExploreMode?: 'orbit' | 'fly', isOriginal: boolean = false): void {\n if (currentCameraMode !== 'explore')\n return;\n // Determine the target mode: use splat-specific mode, or initial mode for original\n const targetMode = isOriginal\n ? initialSplatExploreMode\n : (splatExploreMode || initialSplatExploreMode);\n if (targetMode && cameraControls) {\n const currentMode = cameraControls.mode;\n if (currentMode !== targetMode) {\n cameraControls.setMode(targetMode);\n console.log(`[SplatSwap] Switching explore sub-mode to: ${targetMode}`);\n // Update UI\n updateExploreBtnState(targetMode as 'orbit' | 'fly');\n if (isMobile) {\n setJoystickVisible(uiElements, targetMode === 'fly');\n }\n }\n }\n }\n /**\n * Preload the next splat in the sequence\n */\n async function preloadNextSplat(currentIndex: number): Promise<void> {\n if (!additionalSplats || additionalSplats.length === 0)\n return;\n const nextIndex = (currentIndex + 1) % additionalSplats.length;\n const nextSplat = additionalSplats[nextIndex];\n if (nextSplat && nextSplat.url) {\n await preloadSplat(nextSplat.url);\n }\n }\n /**\n * Load a splat (switch to it, handling show/hide or dispose)\n */\n async function loadSwapSplat(url: string): Promise<void> {\n if (url === currentSplatUrl)\n return;\n if (isLoadingSplat)\n return;\n if (isDestroyed)\n return;\n isLoadingSplat = true;\n console.log('[SplatSwap] Switching to splat:', url);\n try {\n // Hide/dispose current splat\n if (currentSplatUrl && preloadedSplats.has(currentSplatUrl)) {\n if (keepMeshesInMemory) {\n const currentEntity = preloadedSplats.get(currentSplatUrl);\n if (currentEntity)\n hideSplat(currentEntity);\n }\n else {\n disposeSplat(currentSplatUrl);\n }\n }\n else if (splatEntity) {\n // Hide the initial splat entity\n splatEntity.enabled = false;\n }\n // Show new splat if already preloaded\n if (preloadedSplats.has(url)) {\n showSplat(url);\n events.emit('splatChange', { url, isOriginal: false });\n }\n else {\n // Load new splat\n const asset = new pc.Asset('splat-swap-' + Date.now(), 'gsplat', { url });\n await new Promise<void>((resolve, reject) => {\n asset.ready(() => {\n if (isDestroyed) {\n reject(new Error('Viewer destroyed'));\n return;\n }\n const entity = new pc.Entity('splat-swap');\n entity.addComponent('gsplat', { asset, unified: true });\n // Apply scale/position/rotation\n const scale = config.scale || { x: 1, y: 1, z: 1 };\n const invertX = config.invertXScale || false;\n const invertY = config.invertYScale || false;\n const finalScale = {\n x: invertX ? -scale.x : scale.x,\n y: invertY ? -scale.y : scale.y,\n z: (invertX !== invertY) ? -scale.z : scale.z\n };\n entity.setLocalScale(finalScale.x, finalScale.y, finalScale.z);\n const pos = config.position || [0, 0, 0];\n entity.setPosition(pos[0], pos[1], -pos[2]);\n const rot = config.rotation || [0, 0, 0];\n const baseRotX = 180; // Base correction for PlayCanvas coordinate system\n const finalRotDeg = [\n baseRotX + rot[0] * (180 / Math.PI),\n rot[1] * (180 / Math.PI),\n -rot[2] * (180 / Math.PI)\n ];\n entity.setEulerAngles(finalRotDeg[0], finalRotDeg[1], finalRotDeg[2]);\n app.root.addChild(entity);\n preloadedSplats.set(url, entity);\n currentSplatUrl = url;\n events.emit('splatChange', { url, isOriginal: false });\n console.log('[SplatSwap] New splat loaded and shown:', url);\n resolve();\n });\n asset.on('error', (err: unknown) => {\n console.error('[SplatSwap] Load error:', err);\n reject(err);\n });\n app.assets.add(asset);\n app.assets.load(asset);\n });\n }\n // Background preload next splat\n const swapIndex = additionalSplats.findIndex(s => s.url === url);\n if (swapIndex !== -1) {\n preloadNextSplat(swapIndex);\n }\n }\n catch (error) {\n console.error('[SplatSwap] Error switching splat:', error);\n }\n finally {\n isLoadingSplat = false;\n initialSplatLoadDone = true;\n }\n }\n /**\n * Get the primary splat URL (first URL tried in loadSplat)\n */\n function getPrimarySplatUrl(): string {\n if (config.sogUrl)\n return config.sogUrl;\n if (config.splatUrl)\n return config.splatUrl;\n if (config.fallbackUrls && config.fallbackUrls.length > 0)\n return config.fallbackUrls[0];\n return '';\n }\n /**\n * Update splats based on current progress (called from navigation loop)\n */\n function updateSplats(): void {\n if (!additionalSplats || additionalSplats.length === 0)\n return;\n const numWaypoints = config.waypoints?.length || 1;\n const percentage = currentProgress * 100;\n const waypointIndex = Math.round(currentProgress * Math.max(1, numWaypoints - 1));\n // Skip if no meaningful change\n if (Math.abs(percentage - lastSplatCheckProgress) < 0.1 &&\n waypointIndex === lastSplatCheckWaypointIndex) {\n return;\n }\n lastSplatCheckProgress = percentage;\n lastSplatCheckWaypointIndex = waypointIndex;\n // Find the best matching splat based on progress\n // Use separate tracking for waypoint and percentage triggers to avoid\n // comparing different value ranges (waypoint 0-N vs percentage 0-100)\n type SplatSwapType = (typeof additionalSplats)[number];\n let bestWaypointSplat: SplatSwapType | null = null;\n let bestPercentageSplat: SplatSwapType | null = null;\n let bestWaypointTrigger = -Infinity;\n let bestPercentageTrigger = -Infinity;\n for (const splat of additionalSplats) {\n if (splat.waypointIndex !== -1) {\n // Trigger by waypoint index\n if (waypointIndex >= splat.waypointIndex && splat.waypointIndex > bestWaypointTrigger) {\n bestWaypointTrigger = splat.waypointIndex;\n bestWaypointSplat = splat;\n }\n }\n else if (splat.percentage !== -1) {\n // Trigger by percentage\n if (percentage >= splat.percentage && splat.percentage > bestPercentageTrigger) {\n bestPercentageTrigger = splat.percentage;\n bestPercentageSplat = splat;\n }\n }\n }\n // Prefer percentage-based triggers over waypoint-based (percentage is more precise)\n const bestSplat = bestPercentageSplat || bestWaypointSplat;\n // Check if best splat is a \"return to original\" marker\n const isReturnToOriginal = bestSplat && bestSplat.url === ORIGINAL_SPLAT_URL;\n // Determine target URL\n const primaryUrl = getPrimarySplatUrl();\n const targetUrl = bestSplat\n ? (isReturnToOriginal ? primaryUrl : bestSplat.url)\n : primaryUrl;\n // Switch splat if needed\n if (targetUrl && targetUrl !== currentSplatUrl) {\n if (targetUrl === primaryUrl && splatEntity && !currentSplatUrl) {\n // First load - just mark current URL\n currentSplatUrl = primaryUrl;\n }\n else if (targetUrl === primaryUrl && splatEntity) {\n // Return to primary splat\n // Hide swap splats and show primary\n preloadedSplats.forEach((entity, url) => {\n if (url !== primaryUrl) {\n if (keepMeshesInMemory) {\n hideSplat(entity);\n }\n else {\n disposeSplat(url);\n }\n }\n });\n if (splatEntity)\n splatEntity.enabled = true;\n currentSplatUrl = primaryUrl;\n events.emit('splatChange', { url: primaryUrl, isOriginal: true });\n console.log('[SplatSwap] Returned to primary splat');\n // Apply the initial splat's explore mode\n applyExploreModeForSplat(undefined, true);\n }\n else {\n // Switch to a different splat\n loadSwapSplat(targetUrl);\n // Apply the splat's explore mode if specified\n if (bestSplat) {\n applyExploreModeForSplat(bestSplat.defaultExploreMode, false);\n }\n }\n // Apply per-splat skybox if specified\n if (bestSplat && bestSplat.skyboxUrl && !isReturnToOriginal) {\n applySkybox(bestSplat.skyboxUrl, bestSplat.skyboxRotation || 0);\n }\n }\n }\n /**\n * Manually switch back to the original/initial splat\n */\n function goToOriginalSplat(): void {\n const primaryUrl = getPrimarySplatUrl();\n if (currentSplatUrl === primaryUrl)\n return;\n // Hide all swap splats\n preloadedSplats.forEach((entity, url) => {\n if (url !== primaryUrl) {\n if (keepMeshesInMemory) {\n hideSplat(entity);\n }\n else {\n disposeSplat(url);\n }\n }\n });\n // Show original splat\n if (splatEntity)\n splatEntity.enabled = true;\n currentSplatUrl = primaryUrl;\n events.emit('splatChange', { url: primaryUrl, isOriginal: true });\n console.log('[SplatSwap] Manually returned to original splat');\n // Apply the initial splat's explore mode\n applyExploreModeForSplat(undefined, true);\n }\n /**\n * Get the URL of the currently displayed splat\n */\n function getCurrentSplatUrl(): string {\n return currentSplatUrl || getPrimarySplatUrl();\n }\n /**\n * Check if currently showing the original splat\n */\n function isShowingOriginalSplat(): boolean {\n return currentSplatUrl === getPrimarySplatUrl() || currentSplatUrl === null;\n }\n /**\n * Apply a skybox (for per-splat skyboxes)\n * Note: For proper cubemap skyboxes, use 6-face URLs or HDR/EXR environment maps\n * This simplified version loads a single texture and creates a basic skybox\n */\n function applySkybox(url: string, rotation: number = 0): void {\n console.log('[SplatSwap] Applying skybox:', url, 'rotation:', rotation);\n // Load skybox as a cubemap texture\n const skyboxAsset = new pc.Asset('skybox-swap-' + Date.now(), 'cubemap', {\n url: url\n }, {\n // Try to load as RGBM for HDR support\n type: pc.TEXTURETYPE_RGBM,\n mipmaps: true\n });\n skyboxAsset.ready(() => {\n if (isDestroyed)\n return;\n try {\n // Set as scene skybox - cast to Texture to satisfy TypeScript\n app.scene.skybox = skyboxAsset.resource as pc.Texture;\n app.scene.skyboxMip = 0; // Use highest quality mip\n // Apply rotation via quaternion (convert radians to Euler then to Quat)\n if (rotation !== 0) {\n const rotQuat = new pc.Quat();\n rotQuat.setFromEulerAngles(0, rotation * (180 / Math.PI), 0);\n app.scene.skyboxRotation = rotQuat;\n }\n console.log('[SplatSwap] Skybox applied successfully');\n }\n catch (error) {\n console.error('[SplatSwap] Error applying skybox:', error);\n }\n });\n skyboxAsset.on('error', (err: unknown) => {\n console.error('[SplatSwap] Skybox load error:', err);\n });\n app.assets.add(skyboxAsset);\n app.assets.load(skyboxAsset);\n }\n // =========================================================\n // END SPLATSWAP SYSTEM\n // =========================================================\n // Progress-based navigation (matching HTML export)\n let currentProgress = 0; // 0 to 1\n let targetProgress = 0; // Scroll target for inertia (currentProgress lerps toward this)\n let isAnimatingProgress = false; // Flag to skip inertia during button animations\n const totalDuration = config.waypoints?.reduce((sum, wp) => sum + (wp.duration || 2000), 0) || 1;\n // Convert autoPlaySpeed from HTML format (per-frame path units) to progress per second\n // HTML: scrollTarget += autoPlaySpeed * animationRatio (per frame, path units 0 to pathLength-1)\n // NPM: progress += playbackSpeed * deltaTime (per frame, progress 0 to 1)\n // pathLength = (numWaypoints - 1) * 20 subdivisions\n // At 60fps: autoPlaySpeed * 60 path units per second\n // Progress per second = (autoPlaySpeed * 60) / pathLength\n const numWaypoints = config.waypoints?.length || 1;\n const pathLength = Math.max(1, (numWaypoints - 1) * 20);\n const playbackSpeed = config.autoplaySpeed !== undefined\n ? (config.autoplaySpeed * 60) / pathLength\n : 1000 / totalDuration;\n // Normalize loopMode: handle boolean values (true='loop', false='none') and string values\n // Scene data may have boolean loopMode from older versions\n const rawLoopMode = config.loopMode as unknown;\n let loopMode: 'loop' | 'pingpong' | 'none';\n if (rawLoopMode === true) {\n loopMode = 'loop';\n }\n else if (rawLoopMode === false) {\n loopMode = 'none';\n }\n else if (rawLoopMode === 'loop' || rawLoopMode === 'pingpong' || rawLoopMode === 'none') {\n loopMode = rawLoopMode;\n }\n else {\n loopMode = 'loop'; // Default to 'loop' for backward compatibility\n }\n let playbackDirection = 1; // 1 = forward, -1 = backward (for pingpong mode)\n console.log('[StorySplat Viewer] Playback config:', {\n loopMode,\n playbackSpeed,\n totalDuration,\n autoPlay: config.autoPlay,\n rawLoopMode: config.loopMode\n });\n // Damped camera interpolation (matching Babylon's pullStrength behavior)\n let targetCameraPosition: pc.Vec3 | null = null;\n let targetCameraRotation: pc.Quat | null = null;\n // PULL_STRENGTH controls camera smoothness during playback\n // HTML export formula: scrollInterpolationSpeed = 0.01 + transitionSpeed * 0.1\n // With transitionSpeed = 0.5: 0.06 (smoother), transitionSpeed = 1: 0.11, transitionSpeed = 2: 0.21 (snappier)\n const PULL_STRENGTH = 0.01 + (config.transitionSpeed || 1) * 0.1;\n // Elastic user rotation (allows user to look around in tour mode, pulls back to target)\n // Using Euler angles (yaw/pitch) instead of quaternion accumulation to prevent roll drift\n let userYawOffset = 0; // Accumulated yaw offset in degrees\n let userPitchOffset = 0; // Accumulated pitch offset in degrees\n const MAX_PITCH_OFFSET = 60; // Limit pitch to prevent flipping\n let isUserDragging = false;\n let lastPointerX = 0;\n let lastPointerY = 0;\n const USER_ROTATION_SENSITIVITY = 0.3; // How responsive rotation is to drag\n const USER_ROTATION_DECAY = 0.95; // How fast rotation returns to target (0.95 = slow, 0.8 = fast)\n // Pre-calculate waypoint positions, rotations, and FOVs for interpolation\n const waypointPositions: pc.Vec3[] = [];\n const waypointRotations: pc.Quat[] = [];\n const waypointFOVs: number[] = [];\n const defaultFOV = config.fov || 60;\n let targetFOV = defaultFOV;\n if (config.waypoints && config.waypoints.length > 0) {\n config.waypoints.forEach(wp => {\n const pos = wp.position\n ? new pc.Vec3(wp.position.x, wp.position.y, -wp.position.z)\n : new pc.Vec3(0, config.playerHeight || 1.6, 0);\n waypointPositions.push(pos);\n const rot = wp.rotation\n ? convertWaypointRotation(wp.rotation)\n : new pc.Quat();\n waypointRotations.push(rot);\n // Extract FOV from waypoint (convert from radians if needed)\n let fov = wp.fov || defaultFOV;\n if (fov < 3.5) {\n // FOV is in radians, convert to degrees\n fov = fov * (180 / Math.PI);\n }\n waypointFOVs.push(fov);\n });\n // Initialize target position/rotation/FOV immediately for damping to work on load\n if (waypointPositions.length > 0) {\n targetCameraPosition = waypointPositions[0].clone();\n targetCameraRotation = waypointRotations[0].clone();\n targetFOV = waypointFOVs[0];\n }\n }\n // Update camera based on progress (sets targets for damped interpolation)\n function updateCameraFromProgress(progress: number): void {\n if (!waypointControlEnabled || waypointPositions.length < 2)\n return;\n progress = Math.max(0, Math.min(1, progress));\n const numWaypoints = waypointPositions.length;\n // Calculate which segment we're in\n const segmentProgress = progress * (numWaypoints - 1);\n const segmentIndex = Math.min(Math.floor(segmentProgress), numWaypoints - 2);\n const t = segmentProgress - segmentIndex;\n // Calculate target position (for damped interpolation)\n const startPos = waypointPositions[segmentIndex];\n const endPos = waypointPositions[segmentIndex + 1];\n targetCameraPosition = new pc.Vec3(lerp(startPos.x, endPos.x, t), lerp(startPos.y, endPos.y, t), lerp(startPos.z, endPos.z, t));\n // Calculate target rotation (for damped interpolation)\n const startRot = waypointRotations[segmentIndex];\n const endRot = waypointRotations[segmentIndex + 1];\n targetCameraRotation = new pc.Quat();\n targetCameraRotation.slerp(startRot, endRot, t);\n // Calculate target FOV (for damped interpolation)\n const startFOV = waypointFOVs[segmentIndex];\n const endFOV = waypointFOVs[segmentIndex + 1];\n targetFOV = lerp(startFOV, endFOV, t);\n // Update waypoint index and emit event if changed\n const newIndex = Math.round(progress * (numWaypoints - 1));\n if (newIndex !== currentWaypointIndex) {\n const prevIndex = currentWaypointIndex;\n currentWaypointIndex = newIndex;\n // Get current waypoint's camera mode\n const currentWaypoint = config.waypoints![newIndex];\n const waypointCameraMode = currentWaypoint.cameraMode || 'first-person';\n // Update orbit mode state based on per-waypoint camera mode\n if (waypointCameraMode === 'orbit') {\n // Enable orbit-style controls while on tour path\n currentWaypointOrbitEnabled = true;\n // Set orbit target (waypoint position or custom target)\n currentWaypointOrbitTarget = currentWaypoint.orbitTarget\n ? new pc.Vec3(currentWaypoint.orbitTarget.x, currentWaypoint.orbitTarget.y, -(currentWaypoint.orbitTarget.z || 0) // Negate Z for coordinate conversion\n )\n : new pc.Vec3(waypointPositions[newIndex].x, waypointPositions[newIndex].y, waypointPositions[newIndex].z);\n }\n else {\n currentWaypointOrbitEnabled = false;\n currentWaypointOrbitTarget = null;\n }\n events.emit('waypointChange', {\n index: newIndex,\n waypoint: currentWaypoint,\n prevIndex,\n cameraMode: waypointCameraMode\n });\n // Update waypoint audio (play/stop based on waypoint entry/exit)\n updateWaypointAudio(newIndex, prevIndex);\n // Close any open hotspot popup and stop all hotspot audio on waypoint change\n stopAllHotspotAudio();\n stopHotspotPopupMedia(container);\n const popupEl = container.querySelector('.storysplat-hotspot-popup') as HTMLElement;\n if (popupEl)\n popupEl.classList.remove('visible');\n }\n // Emit progress event for UI (ensure clamped value)\n events.emit('progressUpdate', { progress: Math.max(0, Math.min(1, progress)), index: currentWaypointIndex });\n // Update splat swap system based on progress\n updateSplats();\n }\n // Apply damped camera interpolation (matching Babylon's pullStrength behavior)\n // Only active in tour mode - explore/walk modes control the camera directly\n function updateCameraDamping(): void {\n if (!waypointControlEnabled)\n return; // Don't interfere with explore/walk mode cameras\n // Interpolate progress toward target (creates scroll inertia - matching BabylonJS HTML export)\n // Skip during button animations to prevent fighting with animateToProgress easing\n if (!isAnimatingProgress) {\n // BabylonJS formula: scrollPosition += (scrollTarget - scrollPosition) * scrollInterpolationSpeed\n const progressDiff = targetProgress - currentProgress;\n if (Math.abs(progressDiff) > 0.0001) {\n currentProgress += progressDiff * PULL_STRENGTH;\n updateCameraFromProgress(currentProgress);\n }\n }\n if (!targetCameraPosition || !targetCameraRotation)\n return;\n // Lerp position toward target (matching Babylon's 0.1 pullStrength)\n const currentPos = camera.getPosition();\n const newPos = new pc.Vec3();\n newPos.lerp(currentPos, targetCameraPosition, PULL_STRENGTH);\n camera.setPosition(newPos.x, newPos.y, newPos.z);\n // Lerp FOV toward target for smooth transitions between waypoints\n const cameraComponent = camera.camera;\n if (cameraComponent && waypointFOVs.length > 0) {\n const currentFOV = cameraComponent.fov;\n const newFOV = lerp(currentFOV, targetFOV, PULL_STRENGTH);\n cameraComponent.fov = newFOV;\n }\n // Decay user rotation offset when not dragging (elastic pull back to target)\n if (!isUserDragging) {\n userYawOffset *= USER_ROTATION_DECAY;\n userPitchOffset *= USER_ROTATION_DECAY;\n // Snap to zero if very small to avoid floating point drift\n if (Math.abs(userYawOffset) < 0.01)\n userYawOffset = 0;\n if (Math.abs(userPitchOffset) < 0.01)\n userPitchOffset = 0;\n }\n // Build user rotation quaternion from Euler angles (no roll)\n const userRotationOffset = new pc.Quat();\n userRotationOffset.setFromEulerAngles(userPitchOffset, userYawOffset, 0);\n // Combine target rotation with user offset for final rotation target\n const targetWithUserOffset = new pc.Quat();\n targetWithUserOffset.mul2(targetCameraRotation, userRotationOffset);\n // Slerp rotation toward combined target\n const currentRot = camera.getRotation();\n const newRot = new pc.Quat();\n newRot.slerp(currentRot, targetWithUserOffset, PULL_STRENGTH);\n camera.setRotation(newRot);\n }\n // Register camera damping in the render loop\n app.on('update', updateCameraDamping);\n // Set progress directly (for button navigation and API calls)\n function setProgress(progress: number, animate = false): void {\n const clampedProgress = Math.max(0, Math.min(1, progress));\n targetProgress = clampedProgress; // Always update target to keep inertia system in sync\n if (animate) {\n animateToProgress(clampedProgress);\n }\n else {\n currentProgress = clampedProgress; // Snap immediately for non-animated calls\n updateCameraFromProgress(currentProgress);\n }\n }\n // Base transition duration (ms) - can be scaled by transitionSpeed setting\n // transitionSpeed multiplies the base duration (0.5 = faster/250ms, 2 = slower/1000ms)\n // This matches the HTML export where higher transitionSpeed = smoother/slower transitions\n const baseTransitionDuration = 500;\n const transitionDuration = baseTransitionDuration * (config.transitionSpeed || 1);\n // Animate to a target progress\n function animateToProgress(animationTarget: number, duration = transitionDuration): void {\n const startProgress = currentProgress;\n const startTime = performance.now();\n isAnimatingProgress = true; // Prevent inertia from interfering\n const animate = () => {\n const elapsed = performance.now() - startTime;\n const t = Math.min(elapsed / duration, 1);\n const eased = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;\n currentProgress = startProgress + (animationTarget - startProgress) * eased;\n updateCameraFromProgress(currentProgress);\n if (t < 1) {\n requestAnimationFrame(animate);\n }\n else {\n isAnimatingProgress = false; // Animation complete, re-enable inertia\n }\n };\n requestAnimationFrame(animate);\n }\n // Navigation functions\n function goToWaypoint(index: number): void {\n if (!config.waypoints || index < 0 || index >= config.waypoints.length)\n return;\n if (!waypointControlEnabled)\n return;\n const waypointProgress = index / Math.max(1, config.waypoints.length - 1);\n targetProgress = waypointProgress; // Update module-level target for inertia system\n animateToProgress(waypointProgress);\n }\n function nextWaypoint(): void {\n if (!config.waypoints || config.waypoints.length === 0)\n return;\n // scrollButtonMode: 'waypoint' jumps to next waypoint, 'percentage' scrolls by scrollAmount\n const buttonMode = config.scrollButtonMode || 'waypoint';\n if (buttonMode === 'percentage' || buttonMode === 'continuous' || buttonMode === 'incremental') {\n // Percentage mode: scroll by scrollAmount percentage\n const scrollAmountSetting = config.scrollAmount || 10;\n const increment = scrollAmountSetting / 100; // Convert percentage to 0-1\n let newProgress = currentProgress + increment;\n // Handle loop mode for percentage-based navigation\n if (newProgress > 1) {\n if (loopMode === 'loop') {\n newProgress = 0; // Wrap to beginning\n }\n else {\n newProgress = 1; // Clamp to end\n }\n }\n targetProgress = newProgress; // Update module-level target for inertia system\n animateToProgress(newProgress);\n }\n else {\n // Waypoint mode: jump to next waypoint\n let next = currentWaypointIndex + 1;\n // Handle loop mode for waypoint-based navigation\n if (next >= config.waypoints.length) {\n if (loopMode === 'loop') {\n next = 0; // Wrap to first waypoint\n }\n else {\n next = config.waypoints.length - 1; // Stay at last waypoint\n }\n }\n goToWaypoint(next);\n }\n }\n function prevWaypoint(): void {\n if (!config.waypoints || config.waypoints.length === 0)\n return;\n // scrollButtonMode: 'waypoint' jumps to prev waypoint, 'percentage' scrolls by scrollAmount\n const buttonMode = config.scrollButtonMode || 'waypoint';\n if (buttonMode === 'percentage' || buttonMode === 'continuous' || buttonMode === 'incremental') {\n // Percentage mode: scroll by scrollAmount percentage\n const scrollAmountSetting = config.scrollAmount || 10;\n const increment = scrollAmountSetting / 100; // Convert percentage to 0-1\n let newProgress = currentProgress - increment;\n // Handle loop mode for percentage-based navigation\n if (newProgress < 0) {\n if (loopMode === 'loop') {\n newProgress = 1; // Wrap to end\n }\n else {\n newProgress = 0; // Clamp to start\n }\n }\n targetProgress = newProgress; // Update module-level target for inertia system\n animateToProgress(newProgress);\n }\n else {\n // Waypoint mode: jump to prev waypoint\n let prev = currentWaypointIndex - 1;\n // Handle loop mode for waypoint-based navigation\n if (prev < 0) {\n if (loopMode === 'loop') {\n prev = config.waypoints.length - 1; // Wrap to last waypoint\n }\n else {\n prev = 0; // Stay at first waypoint\n }\n }\n goToWaypoint(prev);\n }\n }\n // Continuous playback using requestAnimationFrame\n let lastPlaybackTime = 0;\n let playbackAnimationId: number | null = null;\n function playbackLoop(timestamp: number): void {\n if (!isPlaying)\n return;\n if (lastPlaybackTime === 0)\n lastPlaybackTime = timestamp;\n const deltaTime = (timestamp - lastPlaybackTime) / 1000;\n lastPlaybackTime = timestamp;\n // Increment/decrement progress based on direction\n // Update both currentProgress and targetProgress to keep inertia system in sync\n const progressIncrement = playbackSpeed * deltaTime * playbackDirection;\n currentProgress += progressIncrement;\n targetProgress += progressIncrement;\n // Handle end of tour based on loop mode\n if (currentProgress >= 1) {\n console.log('[StorySplat Viewer] End of tour reached, loopMode:', loopMode);\n switch (loopMode) {\n case 'loop':\n console.log('[StorySplat Viewer] Looping - restarting at beginning');\n currentProgress = 0; // Restart at beginning\n targetProgress = 0;\n break;\n case 'pingpong':\n console.log('[StorySplat Viewer] Pingpong - reversing direction');\n currentProgress = 1;\n targetProgress = 1;\n playbackDirection = -1; // Reverse direction\n break;\n case 'none':\n console.log('[StorySplat Viewer] No loop - stopping playback');\n currentProgress = 1;\n targetProgress = 1;\n pause(); // Stop playback\n events.emit('playbackComplete');\n return;\n default:\n console.log('[StorySplat Viewer] Unknown loopMode, stopping:', loopMode);\n currentProgress = 1;\n targetProgress = 1;\n pause(); // Stop playback\n events.emit('playbackComplete');\n return;\n }\n }\n else if (currentProgress <= 0) {\n // Only relevant for pingpong mode going backward\n switch (loopMode) {\n case 'pingpong':\n console.log('[StorySplat Viewer] Pingpong - reversing to forward');\n currentProgress = 0;\n targetProgress = 0;\n playbackDirection = 1; // Reverse back to forward\n break;\n default:\n currentProgress = 0;\n targetProgress = 0;\n break;\n }\n }\n updateCameraFromProgress(currentProgress);\n playbackAnimationId = requestAnimationFrame(playbackLoop);\n }\n function play(): void {\n // 4DGS frame sequence playback\n if (frameSequencePlayer) {\n frameSequencePlayer.play();\n events.emit('playbackStart');\n return;\n }\n // Waypoint playback\n if (isPlaying || !config.waypoints || config.waypoints.length < 2)\n return;\n isPlaying = true;\n lastPlaybackTime = 0;\n playbackDirection = 1; // Always start going forward\n events.emit('playbackStart');\n playbackAnimationId = requestAnimationFrame(playbackLoop);\n }\n function pause(): void {\n // 4DGS frame sequence pause\n if (frameSequencePlayer) {\n frameSequencePlayer.pause();\n events.emit('playbackStop');\n return;\n }\n // Waypoint pause\n isPlaying = false;\n if (playbackAnimationId) {\n cancelAnimationFrame(playbackAnimationId);\n playbackAnimationId = null;\n }\n events.emit('playbackStop');\n }\n function stop(): void {\n // 4DGS frame sequence stop\n if (frameSequencePlayer) {\n frameSequencePlayer.stop();\n events.emit('playbackStop');\n return;\n }\n // Waypoint stop\n pause();\n setProgress(0);\n }\n // Scroll wheel support\n const scrollCanvas = app.graphicsDevice.canvas as HTMLCanvasElement;\n scrollCanvas.addEventListener('wheel', (e: WheelEvent) => {\n if (!waypointControlEnabled)\n return;\n e.preventDefault();\n // Adjust progress based on scroll using configurable speed and amount\n // Match legacy BabylonJS behavior where scroll speed varies with waypoint count\n const scrollSpeedSetting = config.scrollSpeed || 0.1;\n const scrollAmountSetting = config.scrollAmount || 100;\n // Legacy formula: scrollTarget += deltaY * scrollSpeed (where scrollTarget is 0 to pathLength-1)\n // pathLength = (numWaypoints - 1) * 20 for Catmull-Rom spline with 20 subdivisions\n // Convert to 0-1 progress: progressIncrement = (deltaY * scrollSpeed) / pathLength\n const numWaypoints = config.waypoints?.length || 2;\n const effectivePathLength = Math.max(20, (numWaypoints - 1) * 20); // Min 20 to avoid division issues\n const normalizedDelta = Math.abs(e.deltaY) / 100; // Normalize wheel delta\n const baseIncrement = (normalizedDelta * 100 * scrollSpeedSetting * (scrollAmountSetting / 100)) / effectivePathLength;\n const scrollIncrement = e.deltaY > 0 ? baseIncrement : -baseIncrement;\n // Update target progress - the update loop will lerp currentProgress toward it (creates inertia)\n targetProgress = Math.max(0, Math.min(1, targetProgress + scrollIncrement));\n }, { passive: false });\n // Elastic user rotation - track pointer drag for look-around in tour mode\n // Use capture phase to get events before PlayCanvas input system\n scrollCanvas.addEventListener('pointerdown', (e: PointerEvent) => {\n if (!waypointControlEnabled)\n return; // Only in tour/waypoint mode\n isUserDragging = true;\n lastPointerX = e.clientX;\n lastPointerY = e.clientY;\n }, { capture: true });\n scrollCanvas.addEventListener('pointermove', (e: PointerEvent) => {\n if (!waypointControlEnabled || !isUserDragging)\n return;\n const deltaX = e.clientX - lastPointerX;\n const deltaY = e.clientY - lastPointerY;\n lastPointerX = e.clientX;\n lastPointerY = e.clientY;\n // Convert drag delta to rotation offset in degrees (yaw and pitch only, no roll)\n const yawDelta = -deltaX * USER_ROTATION_SENSITIVITY;\n const pitchDelta = -deltaY * USER_ROTATION_SENSITIVITY;\n // Accumulate yaw and pitch offsets (Euler angles prevent roll drift)\n userYawOffset += yawDelta;\n userPitchOffset += pitchDelta;\n // Clamp pitch to prevent camera flipping\n userPitchOffset = Math.max(-MAX_PITCH_OFFSET, Math.min(MAX_PITCH_OFFSET, userPitchOffset));\n }, { capture: true });\n scrollCanvas.addEventListener('pointerup', () => {\n isUserDragging = false;\n }, { capture: true });\n scrollCanvas.addEventListener('pointerleave', () => {\n isUserDragging = false;\n }, { capture: true });\n // =====================================================\n // HOTSPOT SYSTEM\n // =====================================================\n const hotspotEntities: ViewerRuntimeEntity[] = [];\n // =====================================================\n // PORTAL SYSTEM (Scene-to-Scene Navigation)\n // =====================================================\n const portalEntities: ViewerRuntimeEntity[] = [];\n // Portal confirmation popup (optional)\n const portalPopupEl = (uiElements as UIElementsWithPortalPopup).portalPopup;\n let pendingPortal: PortalData | null = null;\n const hidePortalPopup = () => {\n if (!portalPopupEl)\n return;\n portalPopupEl.classList.remove('visible');\n pendingPortal = null;\n };\n const showPortalPopup = (portal: PortalData) => {\n if (!portalPopupEl)\n return;\n const titleEl = portalPopupEl.querySelector('.storysplat-portal-popup-title') as HTMLElement | null;\n if (titleEl) {\n if (portal.title) {\n titleEl.textContent = portal.title;\n }\n else if (portal.targetSceneName) {\n titleEl.textContent = `${getButtonLabel(currentButtonLabels, 'switchScenes').replace('?', '')} ${portal.targetSceneName}?`;\n }\n else {\n titleEl.textContent = getButtonLabel(currentButtonLabels, 'switchScenes');\n }\n }\n pendingPortal = portal;\n portalPopupEl.classList.add('visible');\n };\n if (portalPopupEl) {\n const confirmBtn = portalPopupEl.querySelector('.storysplat-portal-popup-confirm') as HTMLElement | null;\n const cancelBtn = portalPopupEl.querySelector('.storysplat-portal-popup-cancel') as HTMLElement | null;\n confirmBtn?.addEventListener('click', () => {\n if (pendingPortal) {\n const portal = pendingPortal;\n hidePortalPopup();\n handlePortalNavigation(portal);\n }\n });\n cancelBtn?.addEventListener('click', () => {\n hidePortalPopup();\n });\n }\n // Parse hex color to PlayCanvas Color\n function parseColor(hex: string): pc.Color {\n const r = parseInt(hex.slice(1, 3), 16) / 255;\n const g = parseInt(hex.slice(3, 5), 16) / 255;\n const b = parseInt(hex.slice(5, 7), 16) / 255;\n return new pc.Color(r, g, b);\n }\n // =====================================================\n // SPATIAL AUDIO SYSTEM\n // =====================================================\n interface HotspotAudioElements {\n audio: HTMLAudioElement;\n audioCtx?: AudioContext;\n source?: MediaElementAudioSourceNode;\n panner?: PannerNode;\n updateAudioPosition?: () => void;\n }\n interface VideoSpatialAudio {\n audioCtx: AudioContext;\n source: MediaElementAudioSourceNode;\n panner: PannerNode;\n }\n interface WaypointAudioData {\n entity: pc.Entity;\n waypointIndex: number;\n config: LegacyWaypointAudioConfig;\n slotId: string;\n playing: boolean;\n autoplayTriggered: boolean;\n assetReady: boolean;\n }\n // Track all audio for muting\n const allAudioContexts: AudioContext[] = [];\n const allAudioElements: HTMLAudioElement[] = [];\n let globalMuted = false;\n const storedVolumes = new Map<HTMLAudioElement, number>();\n // Waypoint audio tracking\n const waypointAudioMap = new Map<string, WaypointAudioData>();\n /**\n * Stop all hotspot audio (spatial and non-spatial) and pause any popup media.\n * Called on waypoint transitions and when closing the hotspot popup.\n */\n function stopAllHotspotAudio(): void {\n hotspotEntities.forEach((entity) => {\n if (entity.audioElements) {\n const audioElements = entity.audioElements as HotspotAudioElements;\n const audio = audioElements.audio;\n if (audio && !audio.paused) {\n audio.pause();\n audio.currentTime = 0;\n }\n }\n // Also stop any video hotspots that are playing\n if (entity.videoElement) {\n const video = entity.videoElement as HTMLVideoElement;\n if (!video.paused) {\n video.pause();\n }\n }\n // Reset proximity state so audio can re-trigger on re-entry\n entity.wasInProximity = false;\n });\n }\n /**\n * Stop any media playing inside the hotspot popup (videos, iframes).\n */\n function stopHotspotPopupMedia(containerEl: HTMLElement): void {\n const popup = containerEl.querySelector('.storysplat-hotspot-popup') as HTMLElement;\n if (!popup)\n return;\n // Pause any popup video elements\n popup.querySelectorAll('video').forEach(v => { v.pause(); v.currentTime = 0; });\n // Clear iframe src to stop YouTube embeds etc\n popup.querySelectorAll('iframe').forEach(iframe => { iframe.src = ''; });\n }\n // =====================================================\n // PARTICLE SYSTEM\n // =====================================================\n const particleEntities: Map<string, pc.Entity> = new Map();\n const particleTextures: Map<string, pc.Texture> = new Map();\n // Particle texture URLs (matching HTML export particleSystem.ts)\n const PARTICLE_TEXTURE_URLS: Record<string, string> = {\n flare: 'https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fparticles%2Fflare.png?alt=media&token=ce114781-2ac3-41b2-b9c2-34bda0f6eb13',\n circle: 'https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fparticles%2Fcircle.png?alt=media&token=fd01b475-2b94-4c24-bc83-2a907045715f',\n spark: 'https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fparticles%2Fsparkle.png?alt=media&token=466739a4-ccd7-4295-88c2-dedd681a34f0',\n rain: 'https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fparticles%2Frain.png?alt=media&token=13478487-6259-4906-838c-ad592db893e4',\n smoke: 'https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fparticles%2Fsmoke.png?alt=media&token=6cece4f8-87cf-47f9-974d-c4dd6a649f0f',\n };\n /**\n * Load a particle texture (with caching)\n */\n function loadParticleTexture(name: string, url: string): Promise<pc.Texture> {\n return new Promise((resolve, reject) => {\n // Check cache first\n if (particleTextures.has(name)) {\n resolve(particleTextures.get(name)!);\n return;\n }\n const asset = new pc.Asset(name, 'texture', { url: url });\n asset.on('load', () => {\n // Check if viewer was destroyed while loading\n if (isDestroyed) {\n console.log(`[Particle] Ignoring texture load - viewer was destroyed: ${name}`);\n reject(new Error('Viewer destroyed'));\n return;\n }\n const texture = asset.resource as pc.Texture;\n particleTextures.set(name, texture);\n resolve(texture);\n });\n asset.on('error', (err: unknown) => {\n console.error(`[Particle] Failed to load texture: ${name}`, err);\n reject(err);\n });\n app.assets.add(asset);\n app.assets.load(asset);\n });\n }\n /**\n * Create a particle system entity (matching HTML export createParticleSystem)\n * Enhanced with full feature parity: direction1/direction2, box emitter positioning,\n * scaleX/Y, colorDead, angular speed range, initial rotation range\n */\n function createParticleSystemEntity(psConfig: LegacyParticleConfig): pc.Entity {\n const entity = new pc.Entity(psConfig.name || 'particle-system');\n // Helper to safely get position coordinates\n const getPos = (vec: LegacyVec3 | undefined, defaultVal = 0) => ({\n x: vec?._x ?? vec?.x ?? defaultVal,\n y: vec?._y ?? vec?.y ?? defaultVal,\n z: vec?._z ?? vec?.z ?? defaultVal\n });\n // Helper to safely get color\n const getColor = (color: LegacyColor | undefined): Required<LegacyColor> => ({\n r: color?.r ?? 1,\n g: color?.g ?? 1,\n b: color?.b ?? 1,\n a: color?.a ?? 1\n });\n const emitterPos = getPos(psConfig.emitterPosition, 0);\n const gravity = getPos(psConfig.gravity, 0);\n const color1 = getColor(psConfig.color1);\n const color2 = getColor(psConfig.color2);\n const colorDead = getColor(psConfig.colorDead);\n // Get direction1/direction2 for directional emission (matching HTML export)\n const direction1 = getPos(psConfig.direction1, 0);\n const direction2 = getPos(psConfig.direction2, 0);\n // Calculate lifetime from min/max\n const minLifeTime = psConfig.minLifeTime ?? 1.0;\n const maxLifeTime = psConfig.maxLifeTime ?? 3.0;\n const lifetime = (minLifeTime + maxLifeTime) / 2;\n // Determine emitter shape and calculate extents\n let emitterShape: number = pc.EMITTERSHAPE_BOX;\n let emitterExtents = new pc.Vec3(0.1, 0.1, 0.1);\n let emitterOffset = new pc.Vec3(0, 0, 0);\n if (psConfig.emitterType === 'sphere' || psConfig.emitterShape === 'sphere') {\n emitterShape = pc.EMITTERSHAPE_SPHERE;\n const radius = psConfig.emitterRadius || 0.1;\n emitterExtents = new pc.Vec3(radius, radius, radius);\n }\n else if (psConfig.emitterShape === 'box' && psConfig.emitBoxMin && psConfig.emitBoxMax) {\n // Box emitter with min/max emit box (matching HTML export)\n emitterShape = pc.EMITTERSHAPE_BOX;\n const boxMin = getPos(psConfig.emitBoxMin, 0);\n const boxMax = getPos(psConfig.emitBoxMax, 0);\n // Calculate extents as half the size of the box\n emitterExtents = new pc.Vec3(Math.abs(boxMax.x - boxMin.x) / 2, Math.abs(boxMax.y - boxMin.y) / 2, Math.abs(boxMax.z - boxMin.z) / 2);\n // Calculate center offset from emitter position\n emitterOffset = new pc.Vec3((boxMin.x + boxMax.x) / 2, (boxMin.y + boxMax.y) / 2, -(boxMin.z + boxMax.z) / 2 // Negate Z for coordinate conversion\n );\n }\n else if (psConfig.emitterShape === 'point') {\n // Point emitter - use very small extents\n emitterShape = pc.EMITTERSHAPE_BOX;\n emitterExtents = new pc.Vec3(0.01, 0.01, 0.01);\n }\n else {\n // Default box emitter\n emitterExtents = new pc.Vec3(psConfig.emitterExtents?.x || psConfig.emitterRadius || 0.1, psConfig.emitterExtents?.y || psConfig.emitterRadius || 0.1, psConfig.emitterExtents?.z || psConfig.emitterRadius || 0.1);\n }\n // Determine blend mode (matching HTML export blendModeMap)\n let blendMode: number = pc.BLEND_ADDITIVEALPHA;\n const blendModeStr = psConfig.blendMode || '';\n if (blendModeStr === 'BLENDMODE_STANDARD' || blendModeStr === 'normal' || blendModeStr === 'alpha') {\n blendMode = pc.BLEND_NORMAL;\n }\n else if (blendModeStr === 'BLENDMODE_MULTIPLY' || blendModeStr === 'multiply') {\n blendMode = pc.BLEND_MULTIPLICATIVE;\n }\n else if (blendModeStr === 'BLENDMODE_ONEONE') {\n blendMode = pc.BLEND_ADDITIVE;\n }\n else if (blendModeStr === 'BLENDMODE_ADD' || blendModeStr === 'BLENDMODE_MULTIPLYADD') {\n blendMode = pc.BLEND_ADDITIVEALPHA;\n }\n // Build world velocity graph with gravity\n const gravityX = gravity.x || 0;\n const gravityY = gravity.y || 0; // Don't default to -9.8, use config value\n const gravityZ = -(gravity.z || 0); // Negate Z for coordinate conversion\n // Calculate accumulated velocity at end of lifetime due to gravity\n const endVelX = gravityX * lifetime;\n const endVelY = gravityY * lifetime;\n const endVelZ = gravityZ * lifetime;\n // Get angular speed range (matching HTML export minAngularSpeed/maxAngularSpeed)\n const minAngularSpeed = psConfig.minAngularSpeed ?? 0;\n const maxAngularSpeed = psConfig.maxAngularSpeed ?? (psConfig.angularSpeed ?? 0);\n // Get initial rotation range (matching HTML export minInitialRotation/maxInitialRotation)\n const minInitialRotation = psConfig.minInitialRotation ?? 0;\n const maxInitialRotation = psConfig.maxInitialRotation ?? 0;\n // Get scale ranges (matching HTML export minScaleX/maxScaleX/minScaleY/maxScaleY)\n const minSize = psConfig.minSize ?? 0.1;\n const maxSize = psConfig.maxSize ?? 0.3;\n const minScaleX = psConfig.minScaleX ?? 1;\n const maxScaleX = psConfig.maxScaleX ?? 1;\n const minScaleY = psConfig.minScaleY ?? 1;\n const maxScaleY = psConfig.maxScaleY ?? 1;\n // Calculate emit power range\n const minEmitPower = psConfig.minEmitPower ?? 1;\n const maxEmitPower = psConfig.maxEmitPower ?? 2;\n // Calculate direction-based velocity (average of direction1 and direction2)\n const avgDirX = (direction1.x + direction2.x) / 2;\n const avgDirY = (direction1.y + direction2.y) / 2;\n const avgDirZ = -(direction1.z + direction2.z) / 2; // Negate Z\n // Create particle system with enhanced parameters (full feature parity)\n entity.addComponent('particlesystem', {\n numParticles: psConfig.numParticles || 500,\n lifetime: lifetime,\n rate: psConfig.emitRate || 50,\n emitterShape: emitterShape,\n emitterExtents: emitterExtents,\n emitterRadius: psConfig.emitterRadius || 0.1,\n // Initial rotation range (matching HTML export)\n startAngle: minInitialRotation,\n startAngle2: maxInitialRotation !== 0 ? maxInitialRotation : 360,\n // Speed (radial velocity)\n radialSpeedGraph: new pc.Curve([0, minEmitPower, 1, maxEmitPower]),\n // Local space velocity (for directional emission using direction1/direction2)\n localVelocityGraph: new pc.CurveSet([\n [0, avgDirX * minEmitPower],\n [0, avgDirY * minEmitPower],\n [0, avgDirZ * minEmitPower]\n ]),\n localVelocityGraph2: new pc.CurveSet([\n [0, avgDirX * maxEmitPower],\n [0, avgDirY * maxEmitPower],\n [0, avgDirZ * maxEmitPower]\n ]),\n // World velocity (gravity effect over lifetime)\n velocityGraph: new pc.CurveSet([\n [0, 0, 1, endVelX],\n [0, 0, 1, endVelY],\n [0, 0, 1, endVelZ]\n ]),\n // Scale over lifetime with X/Y scale support\n scaleGraph: new pc.Curve([0, minSize * minScaleX, 1, maxSize * maxScaleX]),\n scaleGraph2: new pc.Curve([0, minSize * minScaleY, 1, maxSize * maxScaleY]),\n // Rotation speed range over lifetime (matching HTML export minAngularSpeed/maxAngularSpeed)\n rotationSpeedGraph: new pc.Curve([0, minAngularSpeed]),\n rotationSpeedGraph2: maxAngularSpeed !== minAngularSpeed\n ? new pc.Curve([0, maxAngularSpeed])\n : undefined,\n // Color over lifetime with colorDead (RGB) - start -> middle -> dead\n colorGraph: new pc.CurveSet([\n [0, color1.r, 0.5, color2.r, 1, colorDead.r],\n [0, color1.g, 0.5, color2.g, 1, colorDead.g],\n [0, color1.b, 0.5, color2.b, 1, colorDead.b]\n ]),\n // Alpha over lifetime with colorDead alpha\n alphaGraph: new pc.Curve([\n 0, color1.a ?? 1,\n 0.5, color2.a ?? 0.8,\n 1, colorDead.a ?? 0\n ]),\n // Rendering settings\n blend: blendMode,\n depthWrite: psConfig.depthWrite ?? false,\n depthSoftening: psConfig.softParticles ?? 0,\n lighting: psConfig.lighting ?? false,\n halfLambert: psConfig.halfLambert ?? false,\n alignToMotion: psConfig.alignToMotion ?? false,\n stretch: psConfig.stretch || 0,\n preWarm: psConfig.preWarm ?? false,\n loop: psConfig.loop ?? true,\n autoPlay: psConfig.autoPlay ?? true,\n // Sorting and orientation\n sort: psConfig.sort ?? 0,\n orientation: psConfig.orientation ?? 0\n });\n // Set local space mode\n if (entity.particlesystem) {\n entity.particlesystem.localSpace = psConfig.localSpace ?? false;\n }\n // Get translationPivot (BabylonJS Vector2 that offsets particle spawn position)\n const translationPivotX = psConfig.translationPivot?.x ?? 0;\n const translationPivotY = psConfig.translationPivot?.y ?? 0;\n // Set position (negate Z for BabylonJS -> PlayCanvas coordinate conversion)\n // Add emitter offset for box emitter positioning and translationPivot offset\n entity.setPosition(emitterPos.x + emitterOffset.x + translationPivotX, emitterPos.y + emitterOffset.y + translationPivotY, -emitterPos.z + emitterOffset.z);\n // Apply renderingGroupId via layer assignment (BabylonJS uses 0-3 for z-ordering)\n // PlayCanvas uses layers - map renderingGroupId to appropriate layer\n const renderingGroupId = psConfig.renderingGroupId ?? 3;\n if (entity.particlesystem && renderingGroupId !== undefined) {\n // In PlayCanvas, higher drawOrder values render on top\n // Map BabylonJS renderingGroupId (0-3) to PlayCanvas draw order\n entity.particlesystem.drawOrder = renderingGroupId;\n }\n console.log(`[Particle] Entity configured at position: (${emitterPos.x + emitterOffset.x + translationPivotX}, ${emitterPos.y + emitterOffset.y + translationPivotY}, ${-emitterPos.z + emitterOffset.z})`);\n console.log(`[Particle] Emitter shape: ${psConfig.emitterShape || 'box'}, extents: ${emitterExtents.x}, ${emitterExtents.y}, ${emitterExtents.z}`);\n console.log(`[Particle] Gravity: (${gravityX}, ${gravityY}, ${gravityZ})`);\n console.log(`[Particle] Colors: start=${JSON.stringify(color1)}, mid=${JSON.stringify(color2)}, dead=${JSON.stringify(colorDead)}`);\n console.log(`[Particle] Angular speed: ${minAngularSpeed} - ${maxAngularSpeed}, Initial rotation: ${minInitialRotation} - ${maxInitialRotation}`);\n console.log(`[Particle] TranslationPivot: (${translationPivotX}, ${translationPivotY}), RenderingGroupId: ${renderingGroupId}`);\n return entity;\n }\n /**\n * Initialize all particle systems from config\n */\n async function initParticleSystems(): Promise<void> {\n console.log('═══════════════════════════════════════');\n console.log('🎆 PARTICLE SYSTEM INITIALIZATION');\n console.log('═══════════════════════════════════════');\n console.log('[Particle] config.particles:', config.particles);\n console.log('[Particle] Type:', typeof config.particles);\n console.log('[Particle] Is Array:', Array.isArray(config.particles));\n if (!config.particles || config.particles.length === 0) {\n console.log('[Particle] ⚠️ No particle systems to create (particles array is empty or undefined)');\n console.log('═══════════════════════════════════════');\n return;\n }\n console.log(`[Particle] 📋 Found ${config.particles.length} particle system(s) to create`);\n for (let i = 0; i < config.particles.length; i++) {\n const ps = config.particles[i];\n console.log(`[Particle] --- Particle System ${i + 1}/${config.particles.length} ---`);\n console.log('[Particle] Raw config:', JSON.stringify(ps, null, 2));\n try {\n // Get texture URL - support custom textures\n const textureName = ps.particleTexture || 'flare';\n let textureUrl: string;\n let textureCacheKey: string;\n if (textureName === 'custom' && ps.customTextureUrl) {\n // Use custom texture URL\n textureUrl = ps.customTextureUrl;\n textureCacheKey = `custom_${ps.id || ps.name || i}`;\n console.log(`[Particle] Custom texture: ${textureUrl.substring(0, 60)}...`);\n }\n else {\n // Use predefined texture\n textureUrl = PARTICLE_TEXTURE_URLS[textureName] || PARTICLE_TEXTURE_URLS['flare'];\n textureCacheKey = textureName;\n console.log(`[Particle] Texture: ${textureName} -> ${textureUrl.substring(0, 60)}...`);\n }\n // Load texture\n console.log('[Particle] Loading texture...');\n const texture = await loadParticleTexture(textureCacheKey, textureUrl);\n console.log('[Particle] ✅ Texture loaded:', texture.name);\n // Create particle system entity\n console.log('[Particle] Creating entity...');\n const entity = createParticleSystemEntity(ps);\n console.log('[Particle] ✅ Entity created:', entity.name);\n // Set the texture on the particle system\n if (entity.particlesystem) {\n entity.particlesystem.colorMap = texture;\n console.log('[Particle] ✅ Texture applied to particle system');\n console.log('[Particle] Particle system properties:', {\n numParticles: entity.particlesystem.numParticles,\n lifetime: entity.particlesystem.lifetime,\n rate: entity.particlesystem.rate,\n loop: entity.particlesystem.loop,\n autoPlay: entity.particlesystem.autoPlay\n });\n }\n else {\n console.warn('[Particle] ⚠️ Entity has no particlesystem component!');\n }\n // Add to scene\n app.root.addChild(entity);\n console.log('[Particle] ✅ Entity added to scene');\n // Log entity position\n const pos = entity.getPosition();\n console.log(`[Particle] Position: (${pos.x.toFixed(2)}, ${pos.y.toFixed(2)}, ${pos.z.toFixed(2)})`);\n // Store reference\n const safeName = (ps.id || ps.name || `particle-${particleEntities.size}`).replace(/[^a-zA-Z0-9]/g, '_');\n particleEntities.set(safeName, entity);\n console.log(`[Particle] ✅ SUCCESS: Created \"${ps.name || safeName}\"`);\n }\n catch (err: unknown) {\n const errObj = err instanceof Error ? err : { message: String(err), stack: '' };\n console.error(`[Particle] ❌ FAILED to create particle system: ${ps.name}`);\n console.error(`[Particle] Error message: ${errObj.message || 'No message'}`);\n console.error(`[Particle] Error stack: ${errObj.stack || 'No stack'}`);\n console.error(`[Particle] Error object:`, err);\n }\n }\n console.log('═══════════════════════════════════════');\n console.log(`[Particle] 🎉 COMPLETE: ${particleEntities.size}/${config.particles.length} particle systems created`);\n console.log('[Particle] Active particle systems:', Array.from(particleEntities.keys()));\n console.log('═══════════════════════════════════════');\n }\n /**\n * Stop all particle systems\n */\n function stopAllParticles(): void {\n particleEntities.forEach((entity) => {\n if (entity.particlesystem) {\n entity.particlesystem.stop();\n }\n });\n }\n /**\n * Start all particle systems\n */\n function startAllParticles(): void {\n particleEntities.forEach((entity) => {\n if (entity.particlesystem) {\n entity.particlesystem.play();\n }\n });\n }\n /**\n * Toggle a specific particle system by ID\n */\n function toggleParticle(id: string): void {\n const entity = particleEntities.get(id);\n if (entity) {\n entity.enabled = !entity.enabled;\n }\n }\n // =====================================================\n // CUSTOM MESH SYSTEM (3D Models - GLB/GLTF)\n // =====================================================\n interface AnimBaseLayerLike {\n states?: string[];\n play?: (state?: string) => void;\n }\n interface AnimComponentLike {\n playing?: boolean;\n speed?: number;\n loop?: boolean;\n baseLayer?: AnimBaseLayerLike;\n animations?: Record<string, unknown>;\n play?: (clip?: string, blendTime?: number) => void;\n pause?: () => void;\n loadStateGraph?: (stateGraph: unknown) => void;\n assignAnimation?: (path: string, asset: unknown, layer?: string) => void;\n }\n interface AnimInfo {\n type: 'pc-anim' | 'anim' | 'animation' | 'glb-skeleton';\n component?: AnimComponentLike;\n node?: pc.Entity;\n modelEntity?: AnimatedEntity;\n asset?: pc.Asset;\n animations?: AnimationResourceLike[];\n animationNames?: string[];\n animComponent?: AnimComponentLike;\n isPlaying?: boolean;\n startTime?: number;\n updateHandler?: ((dt: number) => void) | null;\n currentTime?: number;\n }\n type AnimatedEntity = Omit<pc.Entity, 'anim' | 'animation'> & {\n anim?: AnimComponentLike;\n animation?: AnimComponentLike;\n };\n interface AnimationResourceLike {\n name?: string;\n resource?: {\n name?: string;\n };\n _name?: string;\n _duration?: number;\n duration?: number;\n _curves?: Array<{\n evaluate?: (time: number) => unknown;\n } | null>;\n _paths?: Array<{\n entityPath?: string[];\n component?: string;\n propertyPath?: string[];\n }>;\n }\n interface ContainerResourceWithAnimations extends pc.ContainerResource {\n animations?: AnimationResourceLike[];\n }\n interface CustomMeshData {\n entity: pc.Entity;\n config: CustomMeshConfigLike;\n animComponent?: AnimComponentLike;\n allAnimComponents?: AnimInfo[];\n audioSlotId?: string;\n isAnimPlaying: boolean;\n audioPlaying: boolean;\n }\n const customMeshEntities: Map<string, CustomMeshData> = new Map();\n /**\n * Helper to play an animation component using various methods\n * PlayCanvas has different ways to start animations depending on the setup\n */\n function playAnimComponent(anim: AnimComponentLike): void {\n try {\n // Method 1: Set playing property (simple animations)\n anim.playing = true;\n // Method 2: Use baseLayer.play() if available (Anim component)\n if (anim.baseLayer && typeof anim.baseLayer.play === 'function') {\n // Get the first available state/clip name\n const states = anim.baseLayer.states;\n if (states && states.length > 0) {\n const firstState = states[0];\n if (firstState && firstState !== 'START' && firstState !== 'END' && firstState !== 'ANY') {\n anim.baseLayer.play(firstState);\n console.log('[CustomMesh] Playing animation state:', firstState);\n }\n }\n }\n // Method 3: Direct play() method\n if (typeof anim.play === 'function') {\n anim.play();\n }\n // Method 4: Set speed to ensure animation runs\n if (anim.speed !== undefined) {\n anim.speed = 1;\n }\n console.log('[CustomMesh] Animation started, playing:', anim.playing);\n }\n catch (err) {\n console.error('[CustomMesh] Error playing animation:', err);\n }\n }\n /**\n * Helper to pause an animation component\n */\n function pauseAnimComponent(anim: AnimComponentLike): void {\n try {\n anim.playing = false;\n if (anim.speed !== undefined) {\n anim.speed = 0;\n }\n if (typeof anim.pause === 'function') {\n anim.pause();\n }\n }\n catch (err) {\n console.error('[CustomMesh] Error pausing animation:', err);\n }\n }\n /**\n * Sample an animation track at a specific time\n * Handles keyframe interpolation for position, rotation, and scale\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n function sampleAnimationTrack(track: Record<string, any>, time: number): unknown {\n try {\n const keys: Record<string, any>[] = track.keys || track.keyframes || [];\n if (keys.length === 0)\n return null;\n // Find surrounding keyframes\n let prevKey = keys[0];\n let nextKey = keys[keys.length - 1];\n for (let i = 0; i < keys.length - 1; i++) {\n const currentTime = keys[i].time ?? keys[i][0];\n const nextTime = keys[i + 1].time ?? keys[i + 1][0];\n if (time >= currentTime && time < nextTime) {\n prevKey = keys[i];\n nextKey = keys[i + 1];\n break;\n }\n }\n // Calculate interpolation factor\n const prevTime = prevKey.time ?? prevKey[0] ?? 0;\n const nextTime = nextKey.time ?? nextKey[0] ?? 0;\n const duration = nextTime - prevTime;\n const t = duration > 0 ? (time - prevTime) / duration : 0;\n // Get values (handle both object and array formats)\n const prevValue = prevKey.value ?? prevKey.slice?.(1) ?? prevKey;\n const nextValue = nextKey.value ?? nextKey.slice?.(1) ?? nextKey;\n // Interpolate based on track type\n const trackType: string = track.type || track.property || 'position';\n if (trackType.includes('rotation') || trackType.includes('quaternion')) {\n // Quaternion SLERP interpolation\n if (Array.isArray(prevValue) && prevValue.length >= 4) {\n return slerpQuat(prevValue, nextValue, t);\n }\n return prevValue;\n }\n else {\n // Linear interpolation for position/scale\n if (Array.isArray(prevValue)) {\n return prevValue.map((v: number, i: number) => {\n const nv = nextValue[i] ?? v;\n return v + (nv - v) * t;\n });\n }\n return prevValue;\n }\n }\n catch (e) {\n return null;\n }\n }\n /**\n * Simple quaternion SLERP\n */\n function slerpQuat(a: number[], b: number[], t: number): number[] {\n // Calculate cosine of angle\n let cosom = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];\n // Adjust signs if necessary\n const bAdj = [...b];\n if (cosom < 0) {\n cosom = -cosom;\n bAdj[0] = -bAdj[0];\n bAdj[1] = -bAdj[1];\n bAdj[2] = -bAdj[2];\n bAdj[3] = -bAdj[3];\n }\n // Calculate coefficients\n let scale0: number, scale1: number;\n if (1 - cosom > 0.0001) {\n const omega = Math.acos(cosom);\n const sinom = Math.sin(omega);\n scale0 = Math.sin((1 - t) * omega) / sinom;\n scale1 = Math.sin(t * omega) / sinom;\n }\n else {\n // Use linear interpolation for very close quaternions\n scale0 = 1 - t;\n scale1 = t;\n }\n return [\n scale0 * a[0] + scale1 * bAdj[0],\n scale0 * a[1] + scale1 * bAdj[1],\n scale0 * a[2] + scale1 * bAdj[2],\n scale0 * a[3] + scale1 * bAdj[3]\n ];\n }\n /**\n * Apply an animation value to a target entity\n */\n function applyAnimationValue(rootEntity: pc.Entity, targetPath: string, trackType: string, value: unknown): void {\n try {\n // Parse the target path (e.g., \"Bone001/position\" or just \"position\")\n const parts = targetPath.split('/');\n const property = parts.pop() || targetPath;\n // Find the target node\n let target: pc.Entity | null = rootEntity;\n if (parts.length > 0) {\n const nodeName = parts.join('/');\n target = rootEntity.findByName(nodeName) as pc.Entity || rootEntity;\n }\n if (!target)\n return;\n // Apply the value based on property type\n const normalizedProp = property.toLowerCase();\n if (normalizedProp.includes('position') || normalizedProp.includes('translation')) {\n if (Array.isArray(value) && value.length >= 3) {\n target.setLocalPosition(value[0], value[1], value[2]);\n }\n }\n else if (normalizedProp.includes('rotation') || normalizedProp.includes('quaternion')) {\n if (Array.isArray(value) && value.length >= 4) {\n target.setLocalRotation(new pc.Quat(value[0], value[1], value[2], value[3]));\n }\n else if (Array.isArray(value) && value.length >= 3) {\n // Euler angles\n target.setLocalEulerAngles(value[0], value[1], value[2]);\n }\n }\n else if (normalizedProp.includes('scale')) {\n if (Array.isArray(value) && value.length >= 3) {\n target.setLocalScale(value[0], value[1], value[2]);\n }\n }\n }\n catch (e) {\n // Silently ignore - animation track may reference non-existent node\n }\n }\n /**\n * Enhanced animation player that handles both anim and animation components\n * Also handles the new format with type info including GLB animations\n */\n function playAnimComponentV2(animInfo: AnimInfo): void {\n const { type, component, node, animations } = animInfo;\n console.log('[CustomMesh] Playing animation, type:', type, 'node:', node?.name, 'animations:', animations?.length);\n try {\n if (type === 'pc-anim') {\n // Simple PlayCanvas anim component - matches HTML export approach\n console.log('[CustomMesh] Using simple pc-anim approach (like HTML export)');\n if (!component) return;\n component.playing = true;\n animInfo.isPlaying = true;\n console.log('[CustomMesh] Animation started - playing:', component.playing);\n }\n else if (type === 'animation') {\n // Legacy Animation component\n if (!component) return;\n component.playing = true;\n component.loop = true;\n if (typeof component.play === 'function') {\n // Play the first animation clip\n const clips = Object.keys(component.animations || {});\n if (clips.length > 0) {\n component.play(clips[0], 1);\n console.log('[CustomMesh] Playing legacy animation clip:', clips[0]);\n }\n else {\n component.play();\n }\n }\n }\n else if (type === 'glb-skeleton') {\n // GLB skeleton animation - use PlayCanvas AnimComponent\n console.log('[CustomMesh] Starting GLB skeleton animation playback');\n const { modelEntity, asset, animations: animList } = animInfo;\n if (!modelEntity) return;\n if (animList && animList.length > 0) {\n const firstAnim = animList[0] as AnimationResourceLike;\n console.log('[CustomMesh] Animation clip:', firstAnim);\n console.log('[CustomMesh] Animation name:', firstAnim._name || firstAnim.name);\n console.log('[CustomMesh] Animation duration:', firstAnim._duration || firstAnim.duration);\n try {\n // Try to add AnimComponent if not exists\n if (!modelEntity.anim) {\n console.log('[CustomMesh] Adding AnimComponent to modelEntity');\n // Create simple state graph for the animation\n const stateGraphData = {\n layers: [{\n name: 'Base',\n states: [\n { name: 'START', speed: 1 },\n { name: 'Idle', speed: 1, loop: true }\n ],\n transitions: [{\n from: 'START',\n to: 'Idle',\n time: 0,\n conditions: []\n }]\n }]\n };\n modelEntity.addComponent('anim', {\n activate: true,\n speed: 1\n });\n // Load the state graph\n if (modelEntity.anim) {\n const animComp = modelEntity.anim as unknown as AnimComponentLike;\n animComp.loadStateGraph?.(stateGraphData);\n // Assign the animation to the Idle state\n const animAsset = firstAnim;\n animComp.assignAnimation?.('Base.Idle', animAsset, 'Base');\n console.log('[CustomMesh] AnimComponent added and animation assigned');\n }\n }\n // Now play via the anim component\n if (modelEntity.anim) {\n modelEntity.anim.playing = true;\n modelEntity.anim.speed = 1;\n animInfo.animComponent = modelEntity.anim;\n animInfo.isPlaying = true;\n console.log('[CustomMesh] GLB animation playing via AnimComponent');\n }\n else {\n // Fallback: Manual curve sampling using PlayCanvas AnimTrack format\n console.log('[CustomMesh] Falling back to manual curve sampling');\n animInfo.isPlaying = true;\n animInfo.startTime = Date.now();\n const updateHandler = (dt: number) => {\n if (!animInfo.isPlaying)\n return;\n const elapsed = (Date.now() - (animInfo.startTime || Date.now())) / 1000;\n const duration = firstAnim._duration || firstAnim.duration || 1;\n const loopedTime = elapsed % duration;\n // Sample using PlayCanvas AnimTrack._curves\n try {\n const curves = firstAnim._curves || [];\n curves.forEach((curve: {\n evaluate?: (time: number) => unknown;\n } | null, idx: number) => {\n if (!curve)\n return;\n // Get the target path from AnimTrack\n const paths = firstAnim._paths?.[idx];\n if (!paths || !paths.entityPath)\n return;\n // Find target entity\n let target = modelEntity.findByPath(paths.entityPath.join('/'));\n if (!target)\n target = modelEntity.findByName(paths.entityPath[paths.entityPath.length - 1]);\n if (!target)\n return;\n // Sample the curve at current time\n const value = curve.evaluate?.(loopedTime);\n if (value === undefined || value === null)\n return;\n // Apply based on property type\n const prop = paths.component || paths.propertyPath?.[0];\n if (prop === 'localPosition' || prop === 'position') {\n if (Array.isArray(value)) {\n target.setLocalPosition(value[0], value[1], value[2]);\n }\n }\n else if (prop === 'localRotation' || prop === 'rotation') {\n if (Array.isArray(value) && value.length >= 4) {\n target.setLocalRotation(new pc.Quat(value[0], value[1], value[2], value[3]));\n }\n }\n else if (prop === 'localScale' || prop === 'scale') {\n if (Array.isArray(value)) {\n target.setLocalScale(value[0], value[1], value[2]);\n }\n }\n });\n }\n catch (e) {\n // Silently ignore - curve evaluation errors\n }\n };\n animInfo.updateHandler = updateHandler;\n app.on('update', updateHandler);\n console.log('[CustomMesh] Manual animation playback started');\n }\n }\n catch (err) {\n console.error('[CustomMesh] Error setting up GLB animation:', err);\n }\n }\n }\n else if (type === 'anim' && component) {\n // New Anim component\n component.playing = true;\n component.speed = 1;\n // Try to play via baseLayer\n if (component.baseLayer) {\n const states = component.baseLayer.states || [];\n const playableState = states.find((s: string) => s !== 'START' && s !== 'END' && s !== 'ANY');\n if (playableState) {\n component.baseLayer.play?.(playableState);\n console.log('[CustomMesh] Playing anim state:', playableState);\n }\n }\n }\n // Only log component state if component exists (not for glb-skeleton)\n if (component) {\n console.log('[CustomMesh] Animation component state - playing:', component.playing, 'speed:', component.speed);\n }\n }\n catch (err) {\n console.error('[CustomMesh] Error in playAnimComponentV2:', err);\n }\n }\n /**\n * Enhanced animation pauser\n */\n function pauseAnimComponentV2(animInfo: AnimInfo): void {\n const { type, component } = animInfo;\n if (!component && type !== 'glb-skeleton') return;\n try {\n if (type === 'pc-anim' && component) {\n // Simple PlayCanvas anim component - matches HTML export approach\n component.playing = false;\n animInfo.isPlaying = false;\n console.log('[CustomMesh] pc-anim animation paused');\n }\n else if (type === 'glb-skeleton') {\n // Stop GLB skeleton animation\n animInfo.isPlaying = false;\n // If we have an AnimComponent, pause it\n if (animInfo.animComponent) {\n animInfo.animComponent.playing = false;\n console.log('[CustomMesh] GLB skeleton animation paused via AnimComponent');\n }\n // Remove update handler if using manual playback\n if (animInfo.updateHandler) {\n app.off('update', animInfo.updateHandler);\n animInfo.updateHandler = null;\n console.log('[CustomMesh] GLB manual animation paused');\n }\n }\n else if (component) {\n component.playing = false;\n component.speed = 0;\n if (type === 'animation' && typeof component.pause === 'function') {\n component.pause();\n }\n }\n }\n catch (err) {\n console.error('[CustomMesh] Error pausing animation:', err);\n }\n }\n /**\n * Load and create a custom mesh entity (GLB/GLTF model)\n * Matches HTML export customMeshSystem.ts\n */\n function loadCustomMesh(meshConfig: CustomMeshConfigLike, index: number): pc.Entity | null {\n console.log('[CustomMesh] Loading mesh', index, ':', meshConfig.name, meshConfig);\n // Validate modelUrl before attempting to load\n if (!meshConfig.modelUrl || typeof meshConfig.modelUrl !== 'string' || meshConfig.modelUrl.trim() === '') {\n console.warn('[CustomMesh] Skipping mesh', meshConfig.name, '- no valid modelUrl provided. Config:', meshConfig);\n return null;\n }\n // Create container entity\n const entity = new pc.Entity('custom-mesh-' + index);\n // Position (negate Z for BabylonJS -> PlayCanvas coordinate conversion)\n const pos = meshConfig.position || { x: 0, y: 0, z: 0 };\n entity.setPosition(pos._x ?? pos.x ?? 0, pos._y ?? pos.y ?? 0, -(pos._z ?? pos.z ?? 0));\n // Rotation (convert from radians to degrees)\n if (meshConfig.rotation) {\n const rot = meshConfig.rotation;\n const radToDeg = 180 / Math.PI;\n entity.setEulerAngles((rot._x ?? rot.x ?? 0) * radToDeg, (rot._y ?? rot.y ?? 0) * radToDeg, (rot._z ?? rot.z ?? 0) * radToDeg);\n }\n // Scale\n if (meshConfig.scale) {\n const scale = meshConfig.scale;\n entity.setLocalScale(scale._x ?? scale.x ?? 1, scale._y ?? scale.y ?? 1, scale._z ?? scale.z ?? 1);\n }\n // Load GLB/GLTF model\n const modelUrl = meshConfig.modelUrl.trim();\n console.log('[CustomMesh] Loading model from URL:', modelUrl);\n const modelAsset = new pc.Asset('mesh-model-' + index, 'container', { url: modelUrl });\n app.assets.add(modelAsset);\n // Store mesh data reference\n const meshId = meshConfig.id || `mesh-${index}`;\n const meshData: CustomMeshData = {\n entity,\n config: meshConfig,\n isAnimPlaying: false,\n audioPlaying: false\n };\n customMeshEntities.set(meshId, meshData);\n modelAsset.ready((asset: pc.Asset) => {\n try {\n console.log('[CustomMesh] Model loaded:', meshConfig.name);\n // Validate asset resource before instantiation\n if (!asset || !asset.resource) {\n console.error('[CustomMesh] Invalid asset resource for mesh:', meshConfig.name);\n return;\n }\n // Instantiate the model\n const containerResource = asset.resource as ContainerResourceWithAnimations | undefined;\n const modelEntity = containerResource?.instantiateRenderEntity();\n if (!modelEntity) {\n console.error('[CustomMesh] Failed to instantiate render entity for mesh:', meshConfig.name);\n return;\n }\n entity.addChild(modelEntity);\n // Store reference to model entity for animation\n (entity as ViewerRuntimeEntity).modelEntity = modelEntity;\n // IMPORTANT: Recursively enable all children in the hierarchy\n // Some GLB/GLTF files have nodes disabled by default\n function enableAllChildren(node: pc.Entity): void {\n node.enabled = true;\n if (node.children) {\n node.children.forEach((child) => {\n if (child instanceof pc.Entity) {\n enableAllChildren(child);\n }\n });\n }\n }\n enableAllChildren(modelEntity);\n // Count children for debugging\n let childCount = 0;\n modelEntity.forEach(() => { childCount++; });\n console.log('[CustomMesh] Enabled all children for:', meshConfig.name, '- Total nodes:', childCount);\n // Apply static opacity if specified (not animated mode)\n if (meshConfig.opacityMode !== 'animated' && meshConfig.opacity !== undefined && meshConfig.opacity < 1) {\n // Apply opacity to all mesh instances\n applyOpacityToCustomMesh(entity, meshConfig.opacity);\n console.log('[CustomMesh] Applied static opacity:', meshConfig.opacity, 'to', meshConfig.name);\n }\n // Setup billboarding if enabled (with range support)\n if (meshConfig.billboard) {\n // Store original rotation for when billboard is disabled\n const originalRotation = entity.getEulerAngles().clone();\n // Initialize billboard active state\n (entity as ViewerRuntimeEntity)._billboardActive = !meshConfig.billboardRange; // Active by default if no range\n // Create update handler to make mesh face camera when billboard is active\n app.on('update', () => {\n if (!entity.enabled)\n return;\n // Check if billboard should be active (controlled by updateCustomMeshVisibility)\n const billboardActive = (entity as ViewerRuntimeEntity)._billboardActive;\n if (!billboardActive) {\n // Restore original rotation when billboard is disabled\n entity.setEulerAngles(originalRotation.x, originalRotation.y, originalRotation.z);\n return;\n }\n const camPos = camera.getPosition();\n const meshPos = entity.getPosition();\n // Calculate angle to camera (Y-axis rotation only for billboard)\n const dx = camPos.x - meshPos.x;\n const dz = camPos.z - meshPos.z;\n const angle = Math.atan2(dx, dz) * (180 / Math.PI);\n // Get current rotation and only update Y\n const currentRot = entity.getEulerAngles();\n entity.setEulerAngles(currentRot.x, angle, currentRot.z);\n });\n console.log('[CustomMesh] Billboard enabled for:', meshConfig.name, meshConfig.billboardRange ? '(with range control)' : '(always active)');\n }\n // Handle animations - ALWAYS check for GLB animations regardless of interaction settings\n // Log what's available in the asset for debugging\n const hasGlbAnimations = (containerResource?.animations?.length ?? 0) > 0;\n console.log('[CustomMesh] Asset resource for', meshConfig.name, ':', {\n hasAnimations: hasGlbAnimations,\n animationCount: containerResource?.animations?.length || 0,\n animations: containerResource?.animations?.map((a) => a?.name || a?.resource?.name || 'unnamed') || [],\n interactionConfig: meshConfig.interaction\n });\n // Setup animations if the model has them\n // APPROACH: Match HTML export - prioritize modelEntity.anim first, then fallback\n if (hasGlbAnimations || (meshConfig.interaction && meshConfig.interaction.playModelAnimation)) {\n const allAnimComponents: AnimInfo[] = [];\n // PRIORITY 1: Check if PlayCanvas auto-created an anim component (like HTML export expects)\n const animComponent = (modelEntity as AnimatedEntity).anim;\n if (animComponent) {\n console.log('[CustomMesh] Found modelEntity.anim component - using simple approach');\n allAnimComponents.push({\n type: 'pc-anim',\n component: animComponent,\n modelEntity: modelEntity as unknown as AnimatedEntity\n });\n }\n // PRIORITY 2: Search hierarchy for anim/animation components\n if (allAnimComponents.length === 0) {\n function findAllAnimComponents(node: AnimatedEntity, depth: number = 0): void {\n // Check for new Anim component\n if (node.anim && !allAnimComponents.find((a) => a.component === node.anim)) {\n allAnimComponents.push({ type: 'anim', component: node.anim, node: node as unknown as pc.Entity });\n console.log('[CustomMesh] Found existing ANIM component on:', node.name);\n }\n // Check for legacy Animation component\n if (node.animation) {\n allAnimComponents.push({ type: 'animation', component: node.animation, node: node as unknown as pc.Entity });\n console.log('[CustomMesh] Found ANIMATION (legacy) component on:', node.name);\n }\n if (node.children) {\n node.children.forEach((child) => {\n if (child instanceof pc.Entity) {\n findAllAnimComponents(child as AnimatedEntity, depth + 1);\n }\n });\n }\n }\n findAllAnimComponents(modelEntity as AnimatedEntity);\n }\n // PRIORITY 3: Fall back to manual GLB skeleton animation approach\n if (allAnimComponents.length === 0 && hasGlbAnimations) {\n const animations = containerResource?.animations || [];\n console.log('[CustomMesh] No anim component found - falling back to GLB skeleton approach');\n console.log('[CustomMesh] GLB has', animations.length, 'embedded animations');\n try {\n allAnimComponents.push({\n type: 'glb-skeleton',\n modelEntity: modelEntity as unknown as AnimatedEntity,\n asset: asset,\n animations: animations,\n animationNames: animations.map((a) => a.resource?.name || a.name || 'Animation'),\n isPlaying: false,\n currentTime: 0\n });\n console.log('[CustomMesh] GLB skeleton animations stored:', animations.map((a) => a.resource?.name || a.name));\n }\n catch (err) {\n console.error('[CustomMesh] Error setting up GLB animations:', err);\n }\n }\n if (allAnimComponents.length > 0) {\n // Store all animation components (now with type info)\n meshData.allAnimComponents = allAnimComponents;\n meshData.animComponent = allAnimComponents[0].component;\n console.log('[CustomMesh] Total animation components for', meshConfig.name, ':', allAnimComponents.length, '- Types:', allAnimComponents.map((a) => a.type).join(', '));\n // Auto-play if enabled OR if animationAutoPlay is set\n const shouldAutoPlay = meshConfig.interaction?.animationAutoPlay;\n if (shouldAutoPlay) {\n allAnimComponents.forEach((animInfo) => {\n playAnimComponentV2(animInfo);\n });\n meshData.isAnimPlaying = true;\n console.log('[CustomMesh] Auto-playing all animations for:', meshConfig.name);\n }\n }\n else {\n console.warn('[CustomMesh] No animation components could be set up for mesh:', meshConfig.name);\n }\n }\n else {\n console.log('[CustomMesh] No animations to setup for:', meshConfig.name, '(no GLB animations and playModelAnimation not enabled)');\n }\n // Setup spatial audio if configured\n if (meshConfig.interaction && meshConfig.interaction.playAudio && meshConfig.interaction.audioUrl) {\n setupMeshAudio(entity, meshConfig, meshData);\n }\n // Setup click interaction\n if (meshConfig.interaction) {\n setupMeshClickInteraction(entity, meshConfig, meshData);\n }\n console.log('[CustomMesh] Mesh fully initialized:', meshConfig.name);\n }\n catch (err) {\n console.error('[CustomMesh] Error processing loaded mesh:', meshConfig.name, err);\n }\n });\n modelAsset.on('error', (err: unknown) => {\n console.error('[CustomMesh] Failed to load mesh:', meshConfig.name, err);\n // Remove failed asset to prevent further issues\n app.assets.remove(modelAsset);\n });\n app.assets.load(modelAsset);\n // Visibility range setup\n if (meshConfig.visibilityRange) {\n (entity as ViewerRuntimeEntity).visibilityRange = meshConfig.visibilityRange;\n entity.enabled = meshConfig.enabled !== false;\n }\n else {\n entity.enabled = meshConfig.enabled !== false;\n }\n // Opacity configuration\n (entity as ViewerRuntimeEntity).opacityConfig = {\n mode: meshConfig.opacityMode || 'static',\n value: meshConfig.opacity !== undefined ? meshConfig.opacity : 1\n };\n app.root.addChild(entity);\n return entity;\n }\n /**\n * Setup spatial audio for mesh\n */\n function setupMeshAudio(entity: pc.Entity, meshConfig: CustomMeshConfigLike, meshData: CustomMeshData): void {\n const audioConfig = meshConfig.interaction;\n if (!audioConfig) return;\n const meshId = meshConfig.id || meshConfig.name;\n const slotId = `mesh-audio-${meshId}`;\n // Add sound component to entity\n entity.addComponent('sound', {\n positional: audioConfig.audioSpatial || false,\n distanceModel: audioConfig.audioDistanceModel || 'exponential',\n refDistance: audioConfig.audioRefDistance || 1,\n maxDistance: audioConfig.audioMaxDistance || 100,\n rollOffFactor: audioConfig.audioRolloffFactor || 1,\n slots: {\n [slotId]: {\n name: slotId,\n loop: audioConfig.audioLoop || false,\n autoPlay: false,\n volume: audioConfig.audioVolume !== undefined ? audioConfig.audioVolume : 1,\n pitch: 1\n }\n }\n });\n meshData.audioSlotId = slotId;\n // Load audio asset\n const audioAsset = new pc.Asset(`mesh-audio-asset-${meshId}`, 'audio', { url: audioConfig.audioUrl });\n app.assets.add(audioAsset);\n audioAsset.ready(() => {\n const slot = entity.sound?.slot(slotId);\n if (slot) {\n slot.asset = audioAsset.id;\n }\n console.log('[CustomMesh] Audio loaded for mesh:', meshConfig.name, 'Spatial:', audioConfig.audioSpatial);\n });\n audioAsset.on('error', (err: unknown) => {\n console.error('[CustomMesh] Failed to load mesh audio:', meshConfig.name, err);\n });\n app.assets.load(audioAsset);\n }\n /**\n * Check if a ray intersects any mesh render component in the entity hierarchy\n */\n function rayIntersectsMeshHierarchy(entity: pc.Entity, rayOrigin: pc.Vec3, rayDir: pc.Vec3): boolean {\n // Check this entity's render components\n const render = (entity as ViewerRuntimeEntity).render;\n if (render && render.meshInstances) {\n for (const mi of render.meshInstances) {\n if (mi.aabb) {\n const intersection = rayIntersectsAABB(rayOrigin, rayDir, mi.aabb);\n if (intersection)\n return true;\n }\n }\n }\n // Check model component if present\n const model = (entity as ViewerRuntimeEntity).model;\n if (model && model.meshInstances) {\n for (const mi of model.meshInstances) {\n if (mi.aabb) {\n const intersection = rayIntersectsAABB(rayOrigin, rayDir, mi.aabb);\n if (intersection)\n return true;\n }\n }\n }\n // Recursively check children\n const children = entity.children;\n if (children) {\n for (const child of children) {\n if (child instanceof pc.Entity && rayIntersectsMeshHierarchy(child, rayOrigin, rayDir)) {\n return true;\n }\n }\n }\n return false;\n }\n /**\n * Ray-AABB intersection test\n */\n function rayIntersectsAABB(rayOrigin: pc.Vec3, rayDir: pc.Vec3, aabb: {\n getMin?: () => LegacyVec3 & {\n data?: number[];\n };\n getMax?: () => LegacyVec3 & {\n data?: number[];\n };\n min?: LegacyVec3 & {\n data?: number[];\n };\n max?: LegacyVec3 & {\n data?: number[];\n };\n }): boolean {\n const min = aabb.getMin ? aabb.getMin() : aabb.min;\n const max = aabb.getMax ? aabb.getMax() : aabb.max;\n if (!min || !max)\n return false;\n let tmin = -Infinity;\n let tmax = Infinity;\n const axes = ['x', 'y', 'z'] as const;\n const readAxis = (value: LegacyVec3 & {\n data?: number[];\n }, axis: 'x' | 'y' | 'z', index: number): number => {\n const byAxis = value[axis];\n if (typeof byAxis === 'number')\n return byAxis;\n const byUnderscore = value[`_${axis}` as '_x' | '_y' | '_z'];\n if (typeof byUnderscore === 'number')\n return byUnderscore;\n return value.data?.[index] ?? 0;\n };\n for (let i = 0; i < 3; i++) {\n const axis = axes[i];\n const origin = rayOrigin[axis];\n const dir = rayDir[axis];\n const minVal = readAxis(min, axis, i);\n const maxVal = readAxis(max, axis, i);\n if (Math.abs(dir) < 1e-8) {\n if (origin < minVal || origin > maxVal)\n return false;\n }\n else {\n let t1 = (minVal - origin) / dir;\n let t2 = (maxVal - origin) / dir;\n if (t1 > t2)\n [t1, t2] = [t2, t1];\n tmin = Math.max(tmin, t1);\n tmax = Math.min(tmax, t2);\n if (tmin > tmax)\n return false;\n }\n }\n return tmax >= 0;\n }\n /**\n * Check if a custom mesh has any active interactions\n * Matches HTML export's hasInteractions function\n */\n function hasInteractions(customMesh: CustomMeshConfigLike): boolean {\n const interaction = customMesh.interaction;\n if (!interaction)\n return false;\n return !!(interaction.triggerUIPopup ||\n interaction.playAudio ||\n interaction.playModelAnimation ||\n interaction.triggerDirectLink);\n }\n /**\n * Display mesh content using the hotspot popup system\n * Matches HTML export's displayMeshContent function\n */\n function displayMeshContent(customMesh: CustomMeshConfigLike): void {\n if (!customMesh.interaction)\n return;\n // Create a hotspot-like object to use with the hotspot popup system\n const hotspotData = {\n id: `mesh-content-${customMesh.id}`,\n title: customMesh.interaction.title || customMesh.name,\n information: customMesh.interaction.information,\n photoUrl: customMesh.interaction.photoUrl,\n iframeUrl: customMesh.interaction.iframeUrl,\n externalLinkUrl: customMesh.interaction.externalLinkUrl,\n externalLinkText: customMesh.interaction.externalLinkText,\n // Style options\n backgroundColor: customMesh.interaction.backgroundColor || '#000000',\n textColor: customMesh.interaction.textColor || '#ffffff',\n };\n // Use the viewer UI's hotspot popup if available, otherwise use our own popup\n if ((uiElements as UIElementsWithPortalPopup).showHotspotPopup) {\n (uiElements as UIElementsWithPortalPopup).showHotspotPopup!(hotspotData as HotspotData);\n }\n else {\n showMeshPopup(customMesh);\n }\n }\n /**\n * Hide mesh content popup\n */\n function hideMeshContent(): void {\n // Remove hotspot popup if visible\n const hotspotPopup = document.querySelector('.storysplat-hotspot-popup');\n if (hotspotPopup)\n hotspotPopup.remove();\n // Remove mesh popup if visible\n const meshPopup = document.querySelector('.storysplat-mesh-popup');\n if (meshPopup)\n meshPopup.remove();\n }\n /**\n * Handle direct link trigger for a mesh\n * Matches HTML export's handleDirectLink function\n */\n function handleDirectLink(customMesh: CustomMeshConfigLike): void {\n if (!customMesh.interaction?.triggerDirectLink || !customMesh.interaction.directLinkUrl)\n return;\n window.open(customMesh.interaction.directLinkUrl, '_blank');\n }\n /**\n * Setup hover and click interactions for mesh\n * Enhanced to match HTML export's setupMeshInteractions with per-interaction trigger modes\n */\n function setupMeshClickInteraction(entity: pc.Entity, meshConfig: CustomMeshConfigLike, meshData: CustomMeshData): void {\n // Skip if no active interactions\n if (!hasInteractions(meshConfig)) {\n console.log('[CustomMesh] Skipping interaction setup - no active interactions for:', meshConfig.name);\n return;\n }\n // Get the global activation mode (fallback for per-interaction modes)\n const activationMode = meshConfig.interaction?.activationMode || 'click';\n // Click handler using canvas click + raycast\n const canvasForMesh = app.graphicsDevice.canvas as HTMLCanvasElement;\n // Helper to perform raycast\n const performRaycast = (clientX: number, clientY: number): boolean => {\n const rect = canvasForMesh.getBoundingClientRect();\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n // Raycast from camera through click point\n const from = camera.camera!.screenToWorld(x, y, camera.camera!.nearClip);\n const to = camera.camera!.screenToWorld(x, y, camera.camera!.farClip);\n const dir = new pc.Vec3().sub2(to, from).normalize();\n // Get the model entity (child of container entity)\n const modelEntity = (entity as ViewerRuntimeEntity).modelEntity as pc.Entity | undefined;\n // Check intersection with model entity hierarchy (includes all children)\n if (modelEntity && rayIntersectsMeshHierarchy(modelEntity, from, dir)) {\n return true;\n }\n // Fallback: also check the container entity itself\n if (rayIntersectsMeshHierarchy(entity, from, dir)) {\n return true;\n }\n // Final fallback: distance-based check for small or simple meshes\n const meshPos = entity.getPosition();\n const toMesh = new pc.Vec3().sub2(meshPos, from);\n const t = toMesh.dot(dir);\n if (t > 0) {\n const closestPoint = new pc.Vec3().add2(from, dir.clone().mulScalar(t));\n const distance = closestPoint.distance(meshPos);\n const scale = entity.getLocalScale();\n const hitRadius = Math.max(scale.x, scale.y, scale.z) * 1.5; // Larger radius for fallback\n if (distance < hitRadius) {\n return true;\n }\n }\n return false;\n };\n // Get per-interaction trigger modes (matches HTML export pattern)\n const popupTriggerMode = meshConfig.interaction?.popupTriggerMode || activationMode;\n const audioTriggerMode = meshConfig.interaction?.audioTriggerMode || activationMode;\n const animationTriggerMode = meshConfig.interaction?.animationTriggerMode || activationMode;\n const directLinkTriggerMode = meshConfig.interaction?.directLinkTriggerMode || activationMode;\n // Track hover state for this mesh\n let isHovering = false;\n /**\n * Handle HOVER IN interactions\n * Matches HTML export's OnPointerOverTrigger behavior\n */\n const handleHoverIn = () => {\n // Change cursor to pointer\n canvasForMesh.style.cursor = 'pointer';\n // UI Popup - trigger on hover if popupTriggerMode is 'hover'\n if (popupTriggerMode === 'hover' && meshConfig.interaction?.triggerUIPopup) {\n displayMeshContent(meshConfig);\n }\n // Audio - trigger on hover if audioTriggerMode is 'hover'\n if (audioTriggerMode === 'hover' && meshConfig.interaction?.playAudio && meshData.audioSlotId) {\n const slot = entity.sound?.slot(meshData.audioSlotId);\n if (slot && !slot.isPlaying) {\n slot.play();\n meshData.audioPlaying = true;\n console.log('[CustomMesh] Playing audio on hover for:', meshConfig.name);\n }\n }\n // Animation - trigger on hover if animationTriggerMode is 'hover'\n if (animationTriggerMode === 'hover' && meshConfig.interaction?.playModelAnimation) {\n const allAnimComponents = meshData.allAnimComponents;\n if (allAnimComponents && allAnimComponents.length > 0 && !meshData.isAnimPlaying) {\n allAnimComponents.forEach((animInfo) => playAnimComponentV2(animInfo));\n meshData.isAnimPlaying = true;\n console.log('[CustomMesh] Playing animation on hover for:', meshConfig.name);\n }\n }\n // Direct link - trigger on hover if directLinkTriggerMode is 'hover'\n if (directLinkTriggerMode === 'hover' && meshConfig.interaction?.triggerDirectLink) {\n handleDirectLink(meshConfig);\n }\n };\n /**\n * Handle HOVER OUT interactions\n * Matches HTML export's OnPointerOutTrigger behavior\n */\n const handleHoverOut = () => {\n // Reset cursor\n canvasForMesh.style.cursor = '';\n // UI Popup - hide on hover out if popupTriggerMode is 'hover'\n if (popupTriggerMode === 'hover' && meshConfig.interaction?.triggerUIPopup) {\n hideMeshContent();\n }\n // Audio - stop on hover out if audioTriggerMode is 'hover'\n if (audioTriggerMode === 'hover' && meshConfig.interaction?.playAudio && meshData.audioSlotId) {\n const slot = entity.sound?.slot(meshData.audioSlotId);\n if (slot && slot.isPlaying) {\n slot.stop();\n meshData.audioPlaying = false;\n console.log('[CustomMesh] Stopped audio on hover out for:', meshConfig.name);\n }\n }\n // Animation - pause on hover out if animationTriggerMode is 'hover'\n if (animationTriggerMode === 'hover' && meshConfig.interaction?.playModelAnimation) {\n const allAnimComponents = meshData.allAnimComponents;\n if (allAnimComponents && allAnimComponents.length > 0 && meshData.isAnimPlaying) {\n allAnimComponents.forEach((animInfo) => pauseAnimComponentV2(animInfo));\n meshData.isAnimPlaying = false;\n console.log('[CustomMesh] Paused animation on hover out for:', meshConfig.name);\n }\n }\n };\n /**\n * Handle CLICK interactions\n * Matches HTML export's OnPickTrigger behavior with toggle support\n */\n const handleClick = () => {\n console.log('[CustomMesh] Clicked mesh:', meshConfig.name);\n // UI Popup - trigger on click if popupTriggerMode is 'click'\n if (popupTriggerMode === 'click' && meshConfig.interaction?.triggerUIPopup) {\n displayMeshContent(meshConfig);\n }\n // Animation - toggle on click if animationTriggerMode is 'click'\n if (animationTriggerMode === 'click' && meshConfig.interaction?.playModelAnimation) {\n const allAnimComponents = meshData.allAnimComponents;\n if (allAnimComponents && allAnimComponents.length > 0) {\n if (meshData.isAnimPlaying) {\n allAnimComponents.forEach((animInfo) => pauseAnimComponentV2(animInfo));\n meshData.isAnimPlaying = false;\n console.log('[CustomMesh] Paused', allAnimComponents.length, 'animations on click for:', meshConfig.name);\n }\n else {\n allAnimComponents.forEach((animInfo) => playAnimComponentV2(animInfo));\n meshData.isAnimPlaying = true;\n console.log('[CustomMesh] Playing', allAnimComponents.length, 'animations on click for:', meshConfig.name);\n }\n }\n }\n // Audio - toggle on click if audioTriggerMode is 'click'\n if (audioTriggerMode === 'click' && meshConfig.interaction?.playAudio && meshData.audioSlotId) {\n const slot = entity.sound?.slot(meshData.audioSlotId);\n if (slot) {\n if (slot.isPlaying) {\n slot.pause();\n meshData.audioPlaying = false;\n console.log('[CustomMesh] Paused audio on click for:', meshConfig.name);\n }\n else {\n slot.play();\n meshData.audioPlaying = true;\n console.log('[CustomMesh] Playing audio on click for:', meshConfig.name);\n }\n }\n }\n // Direct link - trigger on click if directLinkTriggerMode is 'click'\n if (directLinkTriggerMode === 'click' && meshConfig.interaction?.triggerDirectLink) {\n handleDirectLink(meshConfig);\n }\n };\n // Click handler\n const meshClickHandler = (e: MouseEvent) => {\n if (performRaycast(e.clientX, e.clientY)) {\n handleClick();\n }\n };\n // Hover handler - handles both hover in and hover out\n const meshHoverHandler = (e: MouseEvent) => {\n const nowHovering = performRaycast(e.clientX, e.clientY);\n if (nowHovering && !isHovering) {\n isHovering = true;\n handleHoverIn();\n }\n else if (!nowHovering && isHovering) {\n isHovering = false;\n handleHoverOut();\n }\n };\n // Mouse leave handler to reset hover state\n const meshLeaveHandler = () => {\n if (isHovering) {\n isHovering = false;\n handleHoverOut();\n }\n };\n canvasForMesh.addEventListener('click', meshClickHandler);\n canvasForMesh.addEventListener('mousemove', meshHoverHandler);\n canvasForMesh.addEventListener('mouseleave', meshLeaveHandler);\n // Store handlers for cleanup\n (entity as ViewerRuntimeEntity).meshClickHandler = meshClickHandler;\n (entity as ViewerRuntimeEntity).meshHoverHandler = meshHoverHandler;\n (entity as ViewerRuntimeEntity).meshLeaveHandler = meshLeaveHandler;\n }\n /**\n * Show mesh popup\n */\n function showMeshPopup(meshConfig: CustomMeshConfigLike): void {\n // Remove existing popup if any\n const existingPopup = document.querySelector('.storysplat-mesh-popup');\n if (existingPopup)\n existingPopup.remove();\n const popup = document.createElement('div');\n popup.className = 'storysplat-mesh-popup';\n popup.style.cssText = `\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: rgba(0, 0, 0, 0.9);\n padding: 30px;\n border-radius: 12px;\n max-width: 600px;\n max-height: 80vh;\n overflow: auto;\n z-index: 100001;\n color: white;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n `;\n const title = document.createElement('h2');\n title.textContent = meshConfig.name || 'Custom Mesh';\n title.style.cssText = 'margin-top: 0; margin-bottom: 16px;';\n popup.appendChild(title);\n if (meshConfig.interaction?.popupContent) {\n const content = document.createElement('p');\n content.textContent = meshConfig.interaction.popupContent;\n content.style.cssText = 'margin: 0; line-height: 1.6;';\n popup.appendChild(content);\n }\n const closeBtn = document.createElement('button');\n closeBtn.textContent = `\\u00d7 ${getButtonLabel(currentButtonLabels, 'close')}`;\n closeBtn.style.cssText = `\n position: absolute;\n top: 10px;\n right: 10px;\n background: rgba(255, 255, 255, 0.2);\n border: none;\n color: white;\n padding: 8px 16px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 16px;\n `;\n closeBtn.onclick = () => popup.remove();\n popup.appendChild(closeBtn);\n document.body.appendChild(popup);\n }\n /**\n * Update mesh visibility based on current waypoint/progress\n * Called when waypoint changes or progress updates\n */\n function updateCustomMeshVisibility(): void {\n const scrollPercent = currentProgress * 100;\n const numWaypoints = config.waypoints?.length || 1;\n const waypointIndex = Math.round(currentProgress * Math.max(1, numWaypoints - 1));\n customMeshEntities.forEach((meshData) => {\n const { entity, config: meshConfig } = meshData;\n const range = (entity as ViewerRuntimeEntity).visibilityRange;\n if (range) {\n let visible = true;\n if (range.type === 'waypoint') {\n // Show only between specific waypoints\n visible = waypointIndex >= range.start && waypointIndex <= range.end;\n }\n else if (range.type === 'percentage') {\n // Show based on scroll percentage (0-100)\n visible = scrollPercent >= range.start && scrollPercent <= range.end;\n }\n entity.enabled = visible;\n }\n // Update opacity if animated\n if (meshConfig.opacityMode === 'animated' && meshConfig.opacityAnimation) {\n const anim = meshConfig.opacityAnimation;\n if (scrollPercent >= anim.startPercent && scrollPercent <= anim.endPercent) {\n const progress = (scrollPercent - anim.startPercent) / (anim.endPercent - anim.startPercent);\n const opacity = anim.startOpacity + (anim.endOpacity - anim.startOpacity) * progress;\n // Apply opacity to all mesh instances in the entity\n applyOpacityToCustomMesh(entity, opacity);\n }\n }\n // Update billboard state based on range (matches HTML export lines 664-674)\n if (meshConfig.billboard && meshConfig.billboardRange) {\n const bRange = meshConfig.billboardRange;\n let billboardActive = false;\n if (bRange.type === 'percentage') {\n billboardActive = scrollPercent >= bRange.start && scrollPercent <= bRange.end;\n }\n else if (bRange.type === 'waypoint') {\n billboardActive = waypointIndex >= bRange.start && waypointIndex <= bRange.end;\n }\n // Store billboard state for the update loop to use\n (entity as ViewerRuntimeEntity)._billboardActive = billboardActive;\n }\n else if (meshConfig.billboard) {\n // Billboard always active if no range specified\n (entity as ViewerRuntimeEntity)._billboardActive = true;\n }\n });\n }\n /**\n * Apply opacity to all mesh render components\n * Uses BLEND_PREMULTIPLIED with depthWrite=true for proper GSplat depth sorting\n */\n function applyOpacityToCustomMesh(entity: pc.Entity, opacity: number): void {\n function applyToEntity(ent: pc.Entity): void {\n if (ent.render && ent.render.meshInstances) {\n ent.render.meshInstances.forEach((meshInstance) => {\n if (meshInstance.material) {\n // Clone material if it's shared to avoid affecting other instances\n const stdMat = meshInstance.material as pc.StandardMaterial & { _isCloned?: boolean };\n if (!stdMat._isCloned) {\n const cloned = meshInstance.material.clone() as pc.StandardMaterial & { _isCloned?: boolean };\n cloned._isCloned = true;\n meshInstance.material = cloned;\n }\n (meshInstance.material as pc.StandardMaterial).opacity = opacity;\n // Use premultiplied alpha with depth write for proper GSplat depth sorting\n meshInstance.material.blendType = pc.BLEND_PREMULTIPLIED;\n meshInstance.material.depthTest = true;\n meshInstance.material.depthWrite = true;\n meshInstance.material.alphaTest = 0.01;\n meshInstance.material.update();\n }\n });\n }\n // Apply to children\n ent.children.forEach((child) => {\n if (child instanceof pc.Entity) {\n applyToEntity(child);\n }\n });\n }\n applyToEntity(entity);\n }\n /**\n * Initialize all custom meshes from config\n */\n async function initCustomMeshes(): Promise<void> {\n if (!config.customMeshes || config.customMeshes.length === 0) {\n console.log('[StorySplat Viewer] No custom meshes to create');\n return;\n }\n console.log(`[StorySplat Viewer] Creating ${config.customMeshes.length} custom meshes...`);\n console.log('[StorySplat Viewer] All custom mesh configs:', config.customMeshes);\n // Defer mesh loading to ensure PlayCanvas is fully initialized\n // This helps avoid race conditions with the asset loader\n setTimeout(() => {\n let loadedCount = 0;\n let skippedCount = 0;\n config.customMeshes!.forEach((meshConfig, index: number) => {\n console.log(`[CustomMesh] Processing mesh ${index}:`, {\n name: meshConfig.name,\n enabled: meshConfig.enabled,\n hasModelUrl: !!meshConfig.modelUrl,\n modelUrl: meshConfig.modelUrl?.substring(0, 100) + '...'\n });\n if (meshConfig.enabled !== false) {\n try {\n const entity = loadCustomMesh(meshConfig, index);\n if (entity) {\n loadedCount++;\n }\n else {\n skippedCount++;\n console.warn(`[CustomMesh] Mesh ${meshConfig.name} returned null (likely missing modelUrl)`);\n }\n }\n catch (err) {\n console.error('[CustomMesh] Error loading mesh', meshConfig.name, ':', err);\n skippedCount++;\n }\n }\n else {\n console.log('[CustomMesh] Skipping disabled mesh:', meshConfig.name, '(enabled =', meshConfig.enabled, ')');\n skippedCount++;\n }\n });\n console.log(`[CustomMesh] Summary: ${loadedCount} loaded, ${skippedCount} skipped`);\n }, 100);\n console.log(`[StorySplat Viewer] ${config.customMeshes.length} custom meshes queued for loading`);\n }\n /**\n * Cleanup all custom meshes\n */\n function cleanupCustomMeshes(): void {\n const canvasForCleanup = app.graphicsDevice.canvas as HTMLCanvasElement;\n customMeshEntities.forEach((meshData) => {\n const entity = meshData.entity;\n const clickHandler = (entity as ViewerRuntimeEntity).meshClickHandler;\n if (clickHandler) {\n canvasForCleanup.removeEventListener('click', clickHandler);\n }\n entity.destroy();\n });\n customMeshEntities.clear();\n }\n // Listen for progress updates to update mesh visibility\n events.on('progressUpdate', () => {\n updateCustomMeshVisibility();\n });\n // =====================================================\n // SKYBOX SYSTEM\n // =====================================================\n let currentSkyboxEntity: pc.Entity | null = null;\n let skyboxFollowCameraHandler: ((dt: number) => void) | null = null;\n /**\n * Initialize skybox from config with IBL (Image-Based Lighting) support\n * Matches HTML export skyboxSystem.ts\n * Supports both nested (config.skybox.url) and flat (config.skyboxUrl) formats\n */\n function initSkybox(): void {\n // Support both config formats: nested object and flat properties\n const skyboxUrl = config.skybox?.url || config.skyboxUrl;\n if (!skyboxUrl) {\n console.log('[StorySplat Viewer] No skybox configured');\n return;\n }\n // Get rotation from either format (value is in radians)\n const skyboxRotation = config.skybox?.rotation ?? config.skyboxRotation ?? 0;\n const skyboxIntensity = config.skybox?.intensity ?? 1.0;\n const enableIBL = config.skybox?.enableIBL ?? true;\n console.log('[StorySplat Viewer] Creating skybox:', skyboxUrl, 'rotation:', skyboxRotation, 'rad =', skyboxRotation * (180 / Math.PI), 'deg', 'IBL:', enableIBL);\n // Check if URL is an HDR/EXR environment map or a regular image\n const isHDR = skyboxUrl.toLowerCase().includes('.hdr') || skyboxUrl.toLowerCase().includes('.exr');\n // Create a large sphere for the skybox (inverted normals) - visual display\n // Remove existing skybox if replacing\n if (currentSkyboxEntity) {\n currentSkyboxEntity.destroy();\n currentSkyboxEntity = null;\n }\n if (skyboxFollowCameraHandler) {\n app.off('update', skyboxFollowCameraHandler);\n skyboxFollowCameraHandler = null;\n }\n const skyboxEntity = new pc.Entity('skybox');\n // Create skybox material\n const skyboxMaterial = new pc.StandardMaterial();\n skyboxMaterial.useLighting = false;\n // We render the viewer from inside the dome. If we simply render back faces,\n // the panorama appears mirrored. Instead, we mirror the mesh (negative X scale)\n // and render front faces, matching BabylonJS PhotoDome behavior.\n skyboxMaterial.cull = pc.CULLFACE_BACK;\n skyboxMaterial.depthTest = false;\n skyboxMaterial.depthWrite = false;\n // Load skybox texture\n const textureAsset = new pc.Asset(`skybox-texture-${Date.now()}`, 'texture', { url: skyboxUrl }, isHDR ? {\n // HDR/EXR equirectangular maps need RGBM decoding in PlayCanvas.\n type: pc.TEXTURETYPE_RGBM,\n mipmaps: true\n } : {\n mipmaps: true\n });\n app.assets.add(textureAsset);\n textureAsset.ready((asset: pc.Asset) => {\n const texture = asset.resource as pc.Texture;\n // Try to improve sampling quality for big panoramas.\n // (These properties exist on pc.Texture; if the runtime build differs, ignore.)\n try {\n texture.minFilter = pc.FILTER_LINEAR_MIPMAP_LINEAR;\n texture.magFilter = pc.FILTER_LINEAR;\n }\n catch {\n // ignore\n }\n // Apply to skybox sphere material\n skyboxMaterial.emissiveMap = texture;\n skyboxMaterial.emissive = new pc.Color(skyboxIntensity, skyboxIntensity, skyboxIntensity);\n skyboxMaterial.update();\n console.log('[StorySplat Viewer] Skybox texture loaded');\n // Apply IBL if enabled - use the texture for environment lighting\n if (enableIBL) {\n try {\n // Set scene exposure for HDR content\n if (isHDR) {\n app.scene.exposure = skyboxIntensity;\n // Set tone mapping via rendering options (if available)\n (app.scene as SceneWithToneMapping).toneMapping = pc.TONEMAP_ACES;\n console.log('[StorySplat Viewer] HDR tone mapping enabled');\n }\n // For IBL, we need to create environment lighting from the texture\n // PlayCanvas uses envAtlas for prefiltered environment maps\n // For a regular 2D texture, we apply ambient light color derived from it\n if (texture) {\n // Set ambient lighting based on skybox\n // Use a neutral ambient derived from skybox intensity\n app.scene.ambientLight = new pc.Color(0.3 * skyboxIntensity, 0.3 * skyboxIntensity, 0.35 * skyboxIntensity);\n // If we have a cubemap (6-face or equirectangular HDR), set it as env atlas\n // For now, enhance ambient based on the skybox\n console.log('[StorySplat Viewer] IBL ambient lighting applied with intensity:', skyboxIntensity);\n }\n }\n catch (iblError) {\n console.warn('[StorySplat Viewer] Failed to apply IBL:', iblError);\n }\n }\n });\n textureAsset.on('error', (err: unknown) => {\n console.error('[StorySplat Viewer] Failed to load skybox texture:', err);\n });\n app.assets.load(textureAsset);\n // Add sphere component\n skyboxEntity.addComponent('render', {\n type: 'sphere',\n material: skyboxMaterial,\n castShadows: false,\n receiveShadows: false\n });\n // Render on the default World layer with a very low drawOrder so the skybox\n // draws first. Combined with depthTest:false + depthWrite:false, this guarantees\n // the skybox fills the background and all scene objects render on top.\n // (LAYERID_SKYBOX is reserved for PlayCanvas's built-in cubemap skybox and does\n // not render custom mesh entities.)\n if (skyboxEntity.render && skyboxEntity.render.meshInstances.length > 0) {\n skyboxEntity.render.meshInstances[0].drawOrder = -10000;\n }\n // Scale to encompass scene (large sphere)\n // Negative X scale mirrors the dome so the panorama is not mirrored when viewed from inside.\n skyboxEntity.setLocalScale(-500, 500, 500);\n // Apply rotation\n if (skyboxRotation !== 0) {\n const rotationDegrees = skyboxRotation * (180 / Math.PI);\n skyboxEntity.setEulerAngles(0, rotationDegrees, 0);\n }\n // Make skybox follow camera position (so it always appears infinite)\n skyboxFollowCameraHandler = () => {\n const camPos = camera.getPosition();\n skyboxEntity.setPosition(camPos.x, camPos.y, camPos.z);\n };\n app.on('update', skyboxFollowCameraHandler);\n app.root.addChild(skyboxEntity);\n currentSkyboxEntity = skyboxEntity;\n console.log('[StorySplat Viewer] Skybox created with rotation:', skyboxRotation, 'intensity:', skyboxIntensity);\n }\n /**\n * Replace or remove the skybox at runtime (editor API).\n */\n function setSkyboxRuntime(url: string | null, rotation?: number): void {\n if (currentSkyboxEntity) {\n currentSkyboxEntity.destroy();\n currentSkyboxEntity = null;\n }\n if (skyboxFollowCameraHandler) {\n app.off('update', skyboxFollowCameraHandler);\n skyboxFollowCameraHandler = null;\n }\n if (url) {\n // Update config so initSkybox reads the new values\n config.skybox = { url, rotation: rotation ?? 0 };\n config.skyboxUrl = url;\n config.skyboxRotation = rotation ?? 0;\n initSkybox();\n }\n }\n // =====================================================\n // CUSTOM LIGHTING SYSTEM\n // =====================================================\n const lightEntities: pc.Entity[] = [];\n /**\n * Convert hex color to PlayCanvas Color\n */\n function hexToColorForLights(hex: string): pc.Color {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (result) {\n return new pc.Color(parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255);\n }\n return new pc.Color(1, 1, 1); // Default white\n }\n /**\n * Create point light\n */\n function createPointLight(lightConfig: LightConfigLike): pc.Entity {\n const entity = new pc.Entity(lightConfig.name || 'Point Light');\n entity.addComponent('light', {\n type: pc.LIGHTTYPE_POINT,\n color: hexToColorForLights(lightConfig.color || '#ffffff'),\n intensity: lightConfig.intensity || 1,\n range: lightConfig.range || 10,\n castShadows: lightConfig.castShadows || false\n });\n // Position (negate Z for coordinate conversion)\n const pos = lightConfig.position;\n if (pos) {\n entity.setPosition(pos._x ?? pos.x ?? 0, pos._y ?? pos.y ?? 0, -(pos._z ?? pos.z ?? 0));\n }\n entity.enabled = lightConfig.enabled !== false;\n return entity;\n }\n /**\n * Create directional light\n */\n function createDirectionalLightEntity(lightConfig: LightConfigLike): pc.Entity {\n const entity = new pc.Entity(lightConfig.name || 'Directional Light');\n entity.addComponent('light', {\n type: pc.LIGHTTYPE_DIRECTIONAL,\n color: hexToColorForLights(lightConfig.color || '#ffffff'),\n intensity: lightConfig.intensity || 1,\n castShadows: lightConfig.castShadows || false\n });\n // Position (negate Z for coordinate conversion)\n const pos = lightConfig.position;\n if (pos) {\n entity.setPosition(pos._x ?? pos.x ?? 0, pos._y ?? pos.y ?? 0, -(pos._z ?? pos.z ?? 0));\n }\n // Direction (rotation)\n const rot = lightConfig.rotation;\n if (rot) {\n const radToDeg = 180 / Math.PI;\n entity.setEulerAngles((rot._x ?? rot.x ?? 0) * radToDeg, (rot._y ?? rot.y ?? 0) * radToDeg, (rot._z ?? rot.z ?? 0) * radToDeg);\n }\n else {\n entity.setEulerAngles(45, 0, 0);\n }\n entity.enabled = lightConfig.enabled !== false;\n return entity;\n }\n /**\n * Create hemispheric light (approximated)\n */\n function createHemisphericLight(lightConfig: LightConfigLike): pc.Entity {\n const entity = new pc.Entity(lightConfig.name || 'Hemispheric Light');\n entity.addComponent('light', {\n type: pc.LIGHTTYPE_DIRECTIONAL,\n color: hexToColorForLights(lightConfig.color || '#ffffff'),\n intensity: lightConfig.intensity || 1,\n castShadows: false\n });\n // Position\n const pos = lightConfig.position;\n if (pos) {\n entity.setPosition(pos._x ?? pos.x ?? 0, pos._y ?? pos.y ?? 0, -(pos._z ?? pos.z ?? 0));\n }\n // Point upwards for hemispheric effect\n entity.setEulerAngles(-90, 0, 0);\n entity.enabled = lightConfig.enabled !== false;\n // If ground color is specified, adjust scene ambient\n if (lightConfig.groundColor) {\n const groundColor = hexToColorForLights(lightConfig.groundColor);\n const skyColor = hexToColorForLights(lightConfig.color || '#ffffff');\n app.scene.ambientLight = new pc.Color((skyColor.r + groundColor.r * 0.3) / 1.3, (skyColor.g + groundColor.g * 0.3) / 1.3, (skyColor.b + groundColor.b * 0.3) / 1.3);\n }\n return entity;\n }\n /**\n * Create ambient light\n */\n function createAmbientLight(lightConfig: LightConfigLike): null {\n const color = hexToColorForLights(lightConfig.color || '#404040');\n const intensity = lightConfig.intensity || 0.4;\n app.scene.ambientLight = new pc.Color(color.r * intensity, color.g * intensity, color.b * intensity);\n console.log('[StorySplat Viewer] Set ambient light:', lightConfig.name || 'Ambient Light');\n return null;\n }\n /**\n * Create spot light\n */\n function createSpotLight(lightConfig: LightConfigLike): pc.Entity {\n const entity = new pc.Entity(lightConfig.name || 'Spot Light');\n // Convert angles from radians to degrees if they come from the editor (stored in radians)\n // PlayCanvas expects degrees for cone angles\n const radToDeg = 180 / Math.PI;\n const angleRad = lightConfig.angle ?? (45 * Math.PI / 180); // Default to 45 degrees in radians\n const angleDeg = angleRad * radToDeg;\n // Inner cone angle is typically smaller than outer - use exponent to derive it\n const exponent = lightConfig.exponent ?? 2;\n const innerAngleDeg = lightConfig.innerConeAngle ?? lightConfig.innerAngle ?? (angleDeg * 0.8);\n const outerAngleDeg = lightConfig.outerConeAngle ?? lightConfig.outerAngle ?? angleDeg;\n entity.addComponent('light', {\n type: pc.LIGHTTYPE_SPOT,\n color: hexToColorForLights(lightConfig.color || '#ffffff'),\n intensity: lightConfig.intensity || 1,\n range: lightConfig.range || 10,\n innerConeAngle: innerAngleDeg,\n outerConeAngle: outerAngleDeg,\n castShadows: lightConfig.castShadows || false,\n shadowBias: lightConfig.shadowBias ?? 0.05,\n normalOffsetBias: lightConfig.normalOffsetBias ?? 0.05\n });\n // Position (negate Z for coordinate conversion)\n const pos = lightConfig.position;\n if (pos) {\n entity.setPosition(pos._x ?? pos.x ?? 0, pos._y ?? pos.y ?? 0, -(pos._z ?? pos.z ?? 0));\n }\n // Direction (rotation) - spot lights need to be pointed at their target\n const rot = lightConfig.rotation;\n if (rot) {\n const radToDeg = 180 / Math.PI;\n entity.setEulerAngles((rot._x ?? rot.x ?? 0) * radToDeg, (rot._y ?? rot.y ?? 0) * radToDeg, (rot._z ?? rot.z ?? 0) * radToDeg);\n }\n else if (lightConfig.direction) {\n // Alternative: direction vector to rotation\n const dir = lightConfig.direction;\n const dirVec = new pc.Vec3(dir._x ?? dir.x ?? 0, dir._y ?? dir.y ?? -1, -(dir._z ?? dir.z ?? 0)).normalize();\n // Calculate rotation from direction\n entity.lookAt(entity.getPosition().x + dirVec.x, entity.getPosition().y + dirVec.y, entity.getPosition().z + dirVec.z);\n }\n else {\n // Default: point downward\n entity.setEulerAngles(90, 0, 0);\n }\n entity.enabled = lightConfig.enabled !== false;\n return entity;\n }\n /**\n * Initialize all custom lights from config\n */\n function initCustomLights(): void {\n if (!config.lights || config.lights.length === 0) {\n console.log('[StorySplat Viewer] No custom lights to create');\n return;\n }\n console.log(`[StorySplat Viewer] Creating ${config.lights.length} custom lights...`);\n config.lights.forEach((lightConfig, index: number) => {\n let entity: pc.Entity | null = null;\n switch (lightConfig.type) {\n case 'point':\n entity = createPointLight(lightConfig);\n break;\n case 'directional':\n entity = createDirectionalLightEntity(lightConfig);\n break;\n case 'hemispheric':\n entity = createHemisphericLight(lightConfig);\n break;\n case 'ambient':\n createAmbientLight(lightConfig);\n break;\n case 'spot':\n entity = createSpotLight(lightConfig);\n break;\n default:\n console.warn('[StorySplat Viewer] Unknown light type:', lightConfig.type);\n return;\n }\n if (entity) {\n app.root.addChild(entity);\n lightEntities.push(entity);\n console.log(`[StorySplat Viewer] Created ${lightConfig.type} light:`, lightConfig.name || `Light ${index}`);\n }\n });\n console.log('[StorySplat Viewer] Lighting setup complete');\n }\n /**\n * Setup spatial audio for a hotspot using Web Audio API\n * Matches the HTML export's setupHotspotAudio function in hotspotSystem.ts\n */\n function setupHotspotAudio(entity: pc.Entity, hotspot: HotspotConfigLike): HotspotAudioElements | null {\n if (!hotspot.audioUrl)\n return null;\n const audio = document.createElement('audio');\n audio.src = hotspot.audioUrl;\n audio.loop = hotspot.audioLoop || false;\n audio.volume = hotspot.audioVolume !== undefined ? hotspot.audioVolume : 1;\n audio.crossOrigin = 'anonymous';\n allAudioElements.push(audio);\n if (hotspot.audioSpatial) {\n // Create AudioContext for spatial audio\n const audioCtx = new (window.AudioContext || (window as WindowWithLegacyAudio).webkitAudioContext)();\n allAudioContexts.push(audioCtx);\n const source = audioCtx.createMediaElementSource(audio);\n const panner = audioCtx.createPanner();\n // Apply spatial settings (matching HTML export)\n panner.panningModel = 'HRTF';\n panner.distanceModel = (hotspot.audioDistanceModel || 'linear') as DistanceModelType; // Match HTML export default\n panner.refDistance = hotspot.audioRefDistance !== undefined ? hotspot.audioRefDistance : 1;\n panner.maxDistance = hotspot.audioMaxDistance !== undefined ? hotspot.audioMaxDistance : 100;\n panner.rolloffFactor = hotspot.audioRolloffFactor !== undefined ? hotspot.audioRolloffFactor : 1;\n // Position audio at hotspot location\n const pos = entity.getPosition();\n panner.setPosition(pos.x, pos.y, pos.z);\n // Connect audio graph\n source.connect(panner);\n panner.connect(audioCtx.destination);\n // Update audio position each frame\n const updateAudioPosition = () => {\n if (!entity || !entity.getPosition)\n return;\n const hotspotPos = entity.getPosition();\n panner.setPosition(hotspotPos.x, hotspotPos.y, hotspotPos.z);\n // Update listener position to camera\n if (camera && camera.getPosition) {\n const camPos = camera.getPosition();\n const camForward = camera.forward;\n const camUp = camera.up;\n if (audioCtx.listener.positionX) {\n // Modern API\n audioCtx.listener.positionX.value = camPos.x;\n audioCtx.listener.positionY.value = camPos.y;\n audioCtx.listener.positionZ.value = camPos.z;\n audioCtx.listener.forwardX.value = camForward.x;\n audioCtx.listener.forwardY.value = camForward.y;\n audioCtx.listener.forwardZ.value = camForward.z;\n audioCtx.listener.upX.value = camUp.x;\n audioCtx.listener.upY.value = camUp.y;\n audioCtx.listener.upZ.value = camUp.z;\n }\n else {\n // Legacy API\n audioCtx.listener.setPosition(camPos.x, camPos.y, camPos.z);\n audioCtx.listener.setOrientation(camForward.x, camForward.y, camForward.z, camUp.x, camUp.y, camUp.z);\n }\n }\n };\n // Register update function\n app.on('update', updateAudioPosition);\n console.log(`[Audio] Spatial audio setup for hotspot: ${hotspot.title}, refDist=${panner.refDistance}, maxDist=${panner.maxDistance}`);\n return { audio, audioCtx, source, panner, updateAudioPosition };\n }\n else {\n // Non-spatial audio\n console.log(`[Audio] Non-spatial audio setup for hotspot: ${hotspot.title}`);\n return { audio };\n }\n }\n /**\n * Setup spatial audio for video hotspot using Web Audio API\n * Makes the video's audio 3D positional based on hotspot location\n */\n function setupVideoSpatialAudio(entity: pc.Entity, video: HTMLVideoElement, hotspot: HotspotConfigLike): VideoSpatialAudio | null {\n // Check if video should have spatial audio\n if (!hotspot.videoSpatialAudio && hotspot.videoMuted !== false) {\n // No spatial audio requested and video is muted\n return null;\n }\n try {\n // Create AudioContext for spatial audio\n const audioCtx = new (window.AudioContext || (window as WindowWithLegacyAudio).webkitAudioContext)();\n allAudioContexts.push(audioCtx);\n // Create source from video element\n const source = audioCtx.createMediaElementSource(video);\n // Create panner for 3D positioning\n const panner = audioCtx.createPanner();\n panner.panningModel = 'HRTF';\n panner.distanceModel = (hotspot.videoDistanceModel || 'linear') as DistanceModelType; // Match HTML export default\n panner.refDistance = hotspot.videoRefDistance !== undefined ? hotspot.videoRefDistance : 1;\n panner.maxDistance = hotspot.videoMaxDistance !== undefined ? hotspot.videoMaxDistance : 100;\n panner.rolloffFactor = hotspot.videoRolloffFactor !== undefined ? hotspot.videoRolloffFactor : 1;\n // Position panner at hotspot location\n const pos = entity.getPosition();\n panner.setPosition(pos.x, pos.y, pos.z);\n // Connect: source -> panner -> destination\n source.connect(panner);\n panner.connect(audioCtx.destination);\n // Update panner and listener position each frame\n app.on('update', () => {\n if (!entity || !entity.getPosition)\n return;\n // Update panner position to hotspot location\n const hotspotPos = entity.getPosition();\n panner.setPosition(hotspotPos.x, hotspotPos.y, hotspotPos.z);\n // Update listener position to camera\n if (camera && camera.getPosition) {\n const camPos = camera.getPosition();\n const camForward = camera.forward;\n const camUp = camera.up;\n if (audioCtx.listener.positionX) {\n // Modern API\n audioCtx.listener.positionX.value = camPos.x;\n audioCtx.listener.positionY.value = camPos.y;\n audioCtx.listener.positionZ.value = camPos.z;\n audioCtx.listener.forwardX.value = camForward.x;\n audioCtx.listener.forwardY.value = camForward.y;\n audioCtx.listener.forwardZ.value = camForward.z;\n audioCtx.listener.upX.value = camUp.x;\n audioCtx.listener.upY.value = camUp.y;\n audioCtx.listener.upZ.value = camUp.z;\n }\n else {\n // Legacy API\n audioCtx.listener.setPosition(camPos.x, camPos.y, camPos.z);\n audioCtx.listener.setOrientation(camForward.x, camForward.y, camForward.z, camUp.x, camUp.y, camUp.z);\n }\n }\n });\n console.log(`[Audio] Video spatial audio setup for hotspot: ${hotspot.title}, refDist=${panner.refDistance}, maxDist=${panner.maxDistance}`);\n return { audioCtx, source, panner };\n }\n catch (err) {\n console.warn('[Audio] Failed to setup video spatial audio:', err);\n return null;\n }\n }\n /**\n * Setup waypoint audio for audio interactions\n * Matches the HTML export's audioSystem.ts\n */\n function setupWaypointAudio(): void {\n if (!config.waypoints || config.waypoints.length === 0)\n return;\n // Extract audio interactions from waypoints\n config.waypoints.forEach((waypoint, waypointIndex: number) => {\n if (waypoint.interactions && Array.isArray(waypoint.interactions)) {\n waypoint.interactions.forEach((interaction) => {\n if (interaction.type === 'audio' && interaction.data) {\n const data = interaction.data as LegacyWaypointAudioConfig;\n const audioId = String(interaction.id || `audio-${waypointIndex}`);\n // Create audio entity\n const audioEntity = new pc.Entity(`waypoint-audio-${audioId}`);\n // Position at waypoint location (with Z negation for PlayCanvas)\n const wpPos = (waypoint.position || { x: 0, y: 0, z: 0 }) as LegacyVec3;\n audioEntity.setPosition(wpPos._x ?? wpPos.x ?? waypoint.x ?? 0, wpPos._y ?? wpPos.y ?? waypoint.y ?? 1.6, -(wpPos._z ?? wpPos.z ?? waypoint.z ?? 0));\n // Configure sound component\n const soundConfig = {\n slots: {\n [audioId]: {\n name: audioId,\n loop: data.loop || false,\n autoPlay: false,\n volume: data.volume !== undefined ? data.volume : 1,\n pitch: 1,\n positional: data.spatialSound || false,\n distanceModel: getPlayCanvasDistanceModel(data.distanceModel || 'exponential'),\n maxDistance: data.maxDistance || 10000,\n refDistance: data.refDistance || 1,\n rollOffFactor: data.rolloffFactor || 1\n }\n }\n };\n audioEntity.addComponent('sound', soundConfig);\n // Load audio asset\n const audioAsset = new pc.Asset(`waypoint-audio-asset-${audioId}`, 'audio', { url: data.url });\n app.assets.add(audioAsset);\n audioAsset.ready(() => {\n const slot = audioEntity.sound?.slot(audioId);\n if (slot) {\n slot.asset = audioAsset.id;\n }\n // Mark as ready for proximity-based playback\n const audioDataRef = waypointAudioMap.get(audioId);\n if (audioDataRef) {\n audioDataRef.assetReady = true;\n }\n console.log(`[Audio] Waypoint audio loaded: ${audioId}, spatialSound=${data.spatialSound}, maxDistance=${data.maxDistance}`);\n });\n app.assets.load(audioAsset);\n // Add to scene\n app.root.addChild(audioEntity);\n // Store audio data\n waypointAudioMap.set(audioId, {\n entity: audioEntity,\n waypointIndex: waypointIndex,\n config: data,\n slotId: audioId,\n playing: false,\n autoplayTriggered: false,\n assetReady: false\n });\n console.log(`[Audio] Waypoint audio created: ${audioId} at waypoint ${waypointIndex}, spatial=${data.spatialSound}`);\n }\n });\n }\n });\n if (waypointAudioMap.size > 0) {\n console.log(`[StorySplat Viewer] Setup ${waypointAudioMap.size} waypoint audio sources`);\n }\n }\n // Map distance model string to PlayCanvas constant\n function getPlayCanvasDistanceModel(modelStr: string): string {\n switch (modelStr) {\n case 'linear':\n return 'linear';\n case 'inverse':\n return 'inverse';\n case 'exponential':\n default:\n return 'exponential';\n }\n }\n // Standalone audio emitters tracking\n interface AudioEmitterData {\n entity: pc.Entity;\n config: AudioEmitterConfig;\n slotId: string;\n playing: boolean;\n assetReady: boolean;\n }\n const audioEmitterMap = new Map<string, AudioEmitterData>();\n /**\n * Setup standalone audio emitters (spatial audio sources not attached to waypoints/hotspots)\n */\n function setupAudioEmitters(): void {\n const emitters = config.audioEmitters;\n if (!emitters || emitters.length === 0)\n return;\n console.log(`[Audio] Setting up ${emitters.length} standalone audio emitters`);\n emitters.forEach((emitter) => {\n if (!emitter.enabled) {\n console.log(`[Audio] Skipping disabled emitter: ${emitter.name || emitter.id}`);\n return;\n }\n if (!emitter.url) {\n console.warn(`[Audio] Emitter has no URL: ${emitter.name || emitter.id}`);\n return;\n }\n const audioId = emitter.id || `emitter-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n const position = emitter.position || { x: 0, y: 0, z: 0 };\n // Create audio entity at emitter position\n const audioEntity = new pc.Entity(`audio-emitter-${audioId}`);\n audioEntity.setPosition(position.x, position.y, position.z);\n // Add sound component with proper spatial settings\n const distanceModel = getPlayCanvasDistanceModel(emitter.distanceModel || 'linear');\n audioEntity.addComponent('sound', {\n positional: emitter.spatialSound !== false,\n refDistance: emitter.refDistance || 1,\n maxDistance: emitter.maxDistance || 100,\n rollOffFactor: emitter.rolloffFactor || 1,\n distanceModel: distanceModel,\n volume: emitter.volume ?? 0.5\n });\n // Add sound slot\n // Note: positional is set on the sound component, not on individual slots\n audioEntity.sound?.addSlot(audioId, {\n volume: emitter.volume ?? 0.5,\n loop: emitter.loop !== false,\n autoPlay: false,\n overlap: false\n });\n // Load audio asset\n const audioAsset = new pc.Asset(`audio-emitter-${audioId}`, 'audio', {\n url: emitter.url\n });\n audioAsset.on('load', () => {\n if (isDestroyed)\n return;\n // Set asset on slot\n const slot = audioEntity.sound?.slot(audioId);\n if (slot) {\n slot.asset = audioAsset.id;\n }\n // Mark as ready\n const emitterData = audioEmitterMap.get(audioId);\n if (emitterData) {\n emitterData.assetReady = true;\n // Start playing if autoplay is enabled\n if (emitter.autoplay !== false && slot) {\n console.log(`[Audio] Autoplay emitter: ${emitter.name || audioId}`);\n slot.play();\n emitterData.playing = true;\n }\n }\n console.log(`[Audio] Emitter loaded: ${emitter.name || audioId}, spatial=${emitter.spatialSound !== false}, maxDistance=${emitter.maxDistance || 100}`);\n });\n app.assets.add(audioAsset);\n app.assets.load(audioAsset);\n // Add to scene\n app.root.addChild(audioEntity);\n // Store emitter data\n audioEmitterMap.set(audioId, {\n entity: audioEntity,\n config: emitter,\n slotId: audioId,\n playing: false,\n assetReady: false\n });\n console.log(`[Audio] Emitter created: ${emitter.name || audioId} at (${position.x}, ${position.y}, ${position.z})`);\n });\n if (audioEmitterMap.size > 0) {\n console.log(`[StorySplat Viewer] Setup ${audioEmitterMap.size} standalone audio emitters`);\n }\n }\n /**\n * Update audio emitter proximity for spatial audio (called every frame)\n */\n function updateAudioEmitterProximity(): void {\n if (audioEmitterMap.size === 0)\n return;\n const cameraPos = camera.getPosition();\n audioEmitterMap.forEach((emitterData, audioId) => {\n const { entity, config, slotId, assetReady, playing } = emitterData;\n // Skip if asset not ready\n if (!assetReady)\n return;\n const slot = entity.sound?.slot(slotId);\n if (!slot)\n return;\n // Only do proximity checks for spatial audio\n if (config.spatialSound !== false) {\n const emitterPos = entity.getPosition();\n const distance = cameraPos.distance(emitterPos);\n const maxDistance = config.maxDistance || 100;\n // Start playing when entering proximity (if not already playing)\n if (distance <= maxDistance && !playing && config.autoplay !== false) {\n if (!slot.isPlaying) {\n slot.play();\n emitterData.playing = true;\n }\n }\n }\n });\n }\n /**\n * Update waypoint audio based on current waypoint (for autoplay on waypoint entry)\n * Called when waypoint changes\n */\n function updateWaypointAudio(currentIndex: number, previousIndex: number): void {\n waypointAudioMap.forEach((audioData, audioId) => {\n const { entity, waypointIndex, config, slotId, autoplayTriggered } = audioData;\n const slot = entity.sound?.slot(slotId);\n if (!slot)\n return;\n // Check if we're entering this waypoint\n const isEntering = currentIndex === waypointIndex && previousIndex !== waypointIndex;\n // Check if we're leaving this waypoint\n const isLeaving = previousIndex === waypointIndex && currentIndex !== waypointIndex;\n // Handle autoplay on waypoint entry (only if autoplay is explicitly true)\n if (isEntering && config.autoplay && !autoplayTriggered) {\n console.log(`[Audio] Autoplay waypoint audio: ${audioId} at waypoint ${waypointIndex}`);\n if (!slot.isPlaying) {\n slot.play();\n audioData.playing = true;\n audioData.autoplayTriggered = true;\n }\n }\n // Handle stop on waypoint exit\n if (isLeaving && config.stopOnExit && audioData.playing) {\n console.log(`[Audio] Stopping waypoint audio on exit: ${audioId}`);\n if (slot.isPlaying) {\n slot.stop();\n audioData.playing = false;\n audioData.autoplayTriggered = false;\n }\n }\n });\n }\n /**\n * Update waypoint audio based on PROXIMITY to audio source\n * For spatial audio, plays when camera is within maxDistance of the audio source\n * Called every frame\n */\n function updateWaypointAudioProximity(): void {\n if (waypointAudioMap.size === 0)\n return;\n const cameraPos = camera.getPosition();\n waypointAudioMap.forEach((audioData, audioId) => {\n const { entity, config, slotId, assetReady, playing } = audioData;\n // Skip if asset not ready yet\n if (!assetReady)\n return;\n const slot = entity.sound?.slot(slotId);\n if (!slot)\n return;\n // For spatial audio, use proximity-based playback\n if (config.spatialSound) {\n const audioPos = entity.getPosition();\n const distance = cameraPos.distance(audioPos);\n const maxDistance = config.maxDistance || 20;\n // Play when entering proximity (within maxDistance)\n if (distance <= maxDistance && !playing) {\n console.log(`[Audio] Proximity play: ${audioId}, distance=${distance.toFixed(2)}, maxDistance=${maxDistance}`);\n slot.play();\n audioData.playing = true;\n }\n // Stop when leaving proximity (only if stopOnExit is true)\n else if (distance > maxDistance && playing && config.stopOnExit) {\n console.log(`[Audio] Proximity stop: ${audioId}, distance=${distance.toFixed(2)}`);\n slot.stop();\n audioData.playing = false;\n }\n }\n });\n }\n // Track which waypoints are \"active\" (camera within trigger distance)\n const activeWaypointTriggers = new Set<number>();\n /**\n * Check waypoint trigger distances and execute/reverse interactions\n * Called every frame in the update loop\n */\n function checkWaypointTriggerDistance(): void {\n if (!config.waypoints?.length)\n return;\n const cameraPos = camera.getPosition();\n config.waypoints.forEach((wp, index: number) => {\n const wpPos = new pc.Vec3(wp.position?.x ?? 0, wp.position?.y ?? 0, -(wp.position?.z ?? 0) // Z-axis negation for PlayCanvas\n );\n const distance = cameraPos.distance(wpPos);\n const triggerDist = wp.triggerDistance ?? 1.0;\n if (distance <= triggerDist) {\n // Entering trigger zone\n if (!activeWaypointTriggers.has(index)) {\n activeWaypointTriggers.add(index);\n console.log(`[StorySplat] Waypoint ${index} triggered (distance: ${distance.toFixed(2)}, threshold: ${triggerDist})`);\n executeWaypointInteractions(wp, index);\n }\n }\n else {\n // Exiting trigger zone\n if (activeWaypointTriggers.has(index)) {\n activeWaypointTriggers.delete(index);\n console.log(`[StorySplat] Waypoint ${index} exited`);\n reverseWaypointInteractions(wp, index);\n }\n }\n });\n }\n /**\n * Execute all interactions on a waypoint when entering trigger distance\n */\n function executeWaypointInteractions(waypoint: WaypointConfigLike, waypointIndex: number): void {\n if (!waypoint.interactions?.length)\n return;\n waypoint.interactions.forEach((interaction) => {\n if (interaction.type === 'audio') {\n // The audio ID matches interaction.id (set in setupWaypointAudio)\n const audioId = interaction.id;\n if (!audioId) return;\n const audioData = waypointAudioMap.get(audioId);\n if (audioData && audioData.assetReady && !audioData.playing) {\n const slot = audioData.entity.sound?.slot(audioData.slotId);\n if (slot) {\n slot.play();\n audioData.playing = true;\n }\n }\n }\n // Info interactions would show popup (if implemented)\n });\n }\n /**\n * Reverse/stop interactions when exiting trigger distance\n */\n function reverseWaypointInteractions(waypoint: WaypointConfigLike, waypointIndex: number): void {\n if (!waypoint.interactions?.length)\n return;\n waypoint.interactions.forEach((interaction) => {\n if (interaction.type === 'audio') {\n const stopOnExit = interaction.data?.stopOnExit ?? false;\n if (stopOnExit) {\n // The audio ID matches interaction.id (set in setupWaypointAudio)\n const audioId = interaction.id;\n if (!audioId) return;\n const audioData = waypointAudioMap.get(audioId);\n if (audioData && audioData.playing) {\n const slot = audioData.entity.sound?.slot(audioData.slotId);\n if (slot) {\n slot.stop();\n audioData.playing = false;\n }\n }\n }\n }\n // Info interactions would hide popup (if implemented)\n });\n }\n /**\n * Global mute/unmute functions\n */\n function muteAllAudio(): void {\n if (globalMuted)\n return;\n // Mute hotspot audio elements\n allAudioElements.forEach(audio => {\n storedVolumes.set(audio, audio.volume);\n audio.volume = 0;\n });\n // Mute waypoint audio\n waypointAudioMap.forEach((audioData) => {\n const slot = audioData.entity.sound?.slot(audioData.slotId);\n if (slot) {\n (slot as AudioSlotWithStoredVolume)._storedVolume = slot.volume;\n slot.volume = 0;\n }\n });\n // Mute audio emitters\n audioEmitterMap.forEach((emitterData) => {\n const slot = emitterData.entity.sound?.slot(emitterData.slotId);\n if (slot) {\n (slot as AudioSlotWithStoredVolume)._storedVolume = slot.volume;\n slot.volume = 0;\n }\n });\n // Also mute any HTML audio/video elements\n document.querySelectorAll('audio, video').forEach((el) => {\n (el as HTMLMediaElement).muted = true;\n });\n globalMuted = true;\n console.log('[Audio] All audio muted');\n }\n function unmuteAllAudio(): void {\n if (!globalMuted)\n return;\n // Unmute hotspot audio elements\n allAudioElements.forEach(audio => {\n const storedVol = storedVolumes.get(audio);\n audio.volume = storedVol !== undefined ? storedVol : 1;\n });\n // Unmute waypoint audio\n waypointAudioMap.forEach((audioData) => {\n const slot = audioData.entity.sound?.slot(audioData.slotId);\n if (slot) {\n const storedVol = (slot as AudioSlotWithStoredVolume)._storedVolume;\n slot.volume = storedVol !== undefined ? storedVol : 1;\n }\n });\n // Unmute audio emitters\n audioEmitterMap.forEach((emitterData) => {\n const slot = emitterData.entity.sound?.slot(emitterData.slotId);\n if (slot) {\n const storedVol = (slot as AudioSlotWithStoredVolume)._storedVolume;\n slot.volume = storedVol !== undefined ? storedVol : 1;\n }\n });\n // Also unmute any HTML audio/video elements\n document.querySelectorAll('audio, video').forEach((el) => {\n (el as HTMLMediaElement).muted = false;\n });\n globalMuted = false;\n console.log('[Audio] All audio unmuted');\n }\n function toggleMuteAll(): boolean {\n if (globalMuted) {\n unmuteAllAudio();\n }\n else {\n muteAllAudio();\n }\n return globalMuted;\n }\n // Track animated GIF textures for cleanup\n const animatedGifs: AnimatedGifTexture[] = [];\n // Create hotspot material for images - entity is hidden until texture loads\n // Supports both static images and animated GIFs with transparency\n function createImageMaterial(imageUrl: string, useLighting: boolean = false, opacity: number = 1, onTextureReady?: () => void): pc.StandardMaterial {\n const material = new pc.StandardMaterial();\n // Configure material for transparency with proper depth handling\n // Use premultiplied alpha blending with depth write for better GSplat compatibility\n material.blendType = pc.BLEND_PREMULTIPLIED;\n material.depthTest = true; // Enable depth testing so hotspots are occluded by splats\n material.depthWrite = true; // Enable depth write for proper GSplat sorting\n material.cull = pc.CULLFACE_NONE; // Double-sided rendering\n material.twoSidedLighting = true; // Proper lighting on both sides\n material.alphaTest = 0.01; // Discard nearly transparent pixels for better sorting\n // For unlit mode: disable all lighting calculations, use only emissive\n if (!useLighting) {\n // Disable diffuse lighting entirely\n material.diffuse = new pc.Color(0, 0, 0);\n // Full white emissive - texture will provide color\n material.emissive = new pc.Color(1, 1, 1);\n // Disable specular\n material.specular = new pc.Color(0, 0, 0);\n material.useLighting = false;\n }\n else {\n material.diffuse = new pc.Color(1, 1, 1);\n }\n material.opacity = opacity;\n material.update();\n // Check if this is a GIF - use AnimatedGifTexture for proper transparency and animation\n if (isGifUrl(imageUrl)) {\n console.log(`[Hotspot] Loading animated GIF: ${imageUrl}`);\n const gifTexture = new AnimatedGifTexture(app, imageUrl, {\n autoPlay: true,\n onReady: () => {\n if (isDestroyed) {\n console.log(`[Hotspot] Ignoring GIF load - viewer was destroyed: ${imageUrl}`);\n gifTexture.destroy();\n return;\n }\n if (gifTexture.texture) {\n gifTexture.texture.premultiplyAlpha = true;\n // Assign texture to material based on lighting mode\n if (!useLighting) {\n material.emissiveMap = gifTexture.texture;\n material.opacityMap = gifTexture.texture;\n }\n else {\n material.diffuseMap = gifTexture.texture;\n material.opacityMap = gifTexture.texture;\n }\n material.opacityMapChannel = 'a';\n material.update();\n console.log(`[Hotspot] GIF texture loaded: ${imageUrl}, useLighting=${useLighting}`);\n if (onTextureReady) {\n onTextureReady();\n }\n }\n },\n onError: (error) => {\n console.error(`[Hotspot] Failed to load GIF: ${imageUrl}`, error);\n // On error, make it slightly visible so user knows something is there\n material.opacity = 0.3;\n material.emissive = new pc.Color(1, 0, 0); // Red to indicate error\n material.update();\n if (onTextureReady) {\n onTextureReady(); // Still call callback so entity becomes visible\n }\n }\n });\n // Track for cleanup\n animatedGifs.push(gifTexture);\n }\n else {\n // Standard image loading\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => {\n // Check if viewer was destroyed while image was loading (React StrictMode race condition)\n if (isDestroyed) {\n console.log(`[Hotspot] Ignoring texture load - viewer was destroyed: ${imageUrl}`);\n return;\n }\n // Create texture with the app's graphics device\n const texture = new pc.Texture(app.graphicsDevice, {\n width: img.width,\n height: img.height,\n format: pc.PIXELFORMAT_RGBA8,\n mipmaps: true,\n minFilter: pc.FILTER_LINEAR_MIPMAP_LINEAR,\n magFilter: pc.FILTER_LINEAR,\n addressU: pc.ADDRESS_CLAMP_TO_EDGE,\n addressV: pc.ADDRESS_CLAMP_TO_EDGE\n });\n // Upload the image data\n texture.setSource(img);\n texture.premultiplyAlpha = true;\n // Assign texture to material based on lighting mode\n if (!useLighting) {\n // Unlit: only use emissive map (no diffuse lighting)\n material.emissiveMap = texture;\n material.opacityMap = texture;\n }\n else {\n // Lit: use diffuse map\n material.diffuseMap = texture;\n material.opacityMap = texture;\n }\n material.opacityMapChannel = 'a';\n material.update();\n console.log(`[Hotspot] Texture loaded for: ${imageUrl}, useLighting=${useLighting}`);\n if (onTextureReady) {\n onTextureReady();\n }\n };\n img.onerror = (err) => {\n console.error(`[Hotspot] Failed to load texture: ${imageUrl}`, err);\n // On error, make it slightly visible so user knows something is there\n material.opacity = 0.3;\n material.emissive = new pc.Color(1, 0, 0); // Red to indicate error\n material.update();\n if (onTextureReady) {\n onTextureReady(); // Still call callback so entity becomes visible\n }\n };\n img.src = imageUrl;\n }\n return material;\n }\n /**\n * Create a single hotspot entity with full rendering (sphere, image, video, gif, billboard, etc.)\n * Used by both createHotspots() at init and the editor mutation API.\n */\n function createSingleHotspot(hotspot: HotspotConfigLike, index: number): pc.Entity {\n const entity = new pc.Entity(`hotspot-${hotspot.id || index}`);\n // Get position (negate Z for BabylonJS -> PlayCanvas coordinate conversion)\n const pos = hotspot.position || { _x: 0, _y: 0, _z: 0 };\n entity.setPosition(pos._x ?? pos.x ?? 0, pos._y ?? pos.y ?? 0, -(pos._z ?? pos.z ?? 0));\n // Get scale\n const rawScale = hotspot.scale || { _x: 1, _y: 1, _z: 1 };\n const scaleObj = typeof rawScale === 'number' ? { x: rawScale, y: rawScale, z: rawScale } as LegacyVec3 : rawScale;\n const sx = Math.abs(scaleObj._x ?? scaleObj.x ?? 1);\n const sy = Math.abs(scaleObj._y ?? scaleObj.y ?? 1);\n const sz = scaleObj._z ?? scaleObj.z ?? 1;\n // Get rotation (convert from radians to degrees, match BabylonJS -> PlayCanvas)\n const rot = hotspot.rotation || { _x: 0, _y: 0, _z: 0 };\n const radToDeg = 180 / Math.PI;\n const rotX = (rot._x ?? rot.x ?? 0) * radToDeg;\n const rotY = (rot._y ?? rot.y ?? 0) * radToDeg;\n const rotZ = (rot._z ?? rot.z ?? 0) * radToDeg;\n entity.setEulerAngles(rotX, rotY, -rotZ);\n // Create geometry based on type\n if (hotspot.type === 'sphere') {\n entity.addComponent('render', {\n type: 'sphere',\n castShadows: false,\n receiveShadows: false\n });\n const material = new pc.StandardMaterial();\n const color = parseColor(hotspot.color || '#ffffff');\n material.diffuse = color;\n material.emissive = color.clone();\n (material.emissive as pc.Color).mulScalar(0.5);\n // For spheres: use full opacity with depth write for proper GSplat occlusion\n // GSplat doesn't sort properly with transparent objects, so make spheres opaque-ish\n const sphereOpacity = hotspot.opacity ?? 0.8;\n if (sphereOpacity >= 0.95) {\n // Fully opaque - use standard opaque rendering\n material.opacity = 1;\n material.blendType = pc.BLEND_NONE;\n material.depthTest = true;\n material.depthWrite = true;\n }\n else {\n // Semi-transparent - use additive blending for better GSplat compatibility\n material.opacity = sphereOpacity;\n material.blendType = pc.BLEND_ADDITIVEALPHA;\n material.depthTest = true;\n material.depthWrite = true; // Write depth to help with sorting\n }\n material.update();\n entity.render!.material = material;\n entity.setLocalScale(sx * 0.2, sy * 0.2, sz * 0.2); // Scale down spheres\n }\n else if (hotspot.type === 'image' && hotspot.imageUrl) {\n entity.addComponent('render', {\n type: 'plane',\n castShadows: false,\n receiveShadows: false\n });\n // Get initial opacity (use startOpacity for animated, opacity for static)\n // Default to 1 (fully visible) - user must explicitly set startOpacity to 0 for fade-in\n let initialOpacity = 1;\n if (hotspot.opacityMode === 'animated' && hotspot.opacityAnimation) {\n // Use startOpacity if defined, otherwise default to 1 (visible)\n initialOpacity = hotspot.opacityAnimation.startOpacity !== undefined\n ? hotspot.opacityAnimation.startOpacity\n : 1;\n }\n else if (hotspot.opacity !== undefined) {\n initialOpacity = hotspot.opacity;\n }\n // Store target opacity for animation system\n (entity as ViewerRuntimeEntity).targetOpacity = initialOpacity;\n (entity as ViewerRuntimeEntity).textureLoaded = false;\n // HIDE entity until texture is loaded to prevent WebGL errors\n (entity as ViewerRuntimeEntity).hiddenUntilTextureLoaded = true;\n entity.enabled = false;\n // Check useLighting setting from hotspot (default to false = unlit)\n const useLighting = hotspot.useLighting === true;\n const material = createImageMaterial(hotspot.imageUrl, useLighting, initialOpacity, () => {\n // Callback when texture is ready - show the entity\n (entity as ViewerRuntimeEntity).textureLoaded = true;\n // Only enable if not hidden by visibility range\n if (!(entity as ViewerRuntimeEntity).visibilityRange || (entity as ViewerRuntimeEntity).shouldBeVisible) {\n entity.enabled = true;\n }\n (entity as ViewerRuntimeEntity).hiddenUntilTextureLoaded = false;\n });\n entity.render!.material = material;\n entity.setLocalScale(sx, sy, sz);\n // Rotate plane to face forward (PlayCanvas planes are horizontal by default)\n entity.rotateLocal(90, 0, 0);\n // Store material reference for opacity animation\n (entity as ViewerRuntimeEntity).hotspotMaterial = material;\n console.log(`[Hotspot] Created image hotspot: ${hotspot.title}, opacity=${initialOpacity}, opacityMode=${hotspot.opacityMode}, useLighting=${useLighting}`);\n }\n else if (hotspot.type === 'video' && hotspot.videoUrl) {\n entity.addComponent('render', {\n type: 'plane',\n castShadows: false,\n receiveShadows: false\n });\n // Detect iOS for alpha video method\n const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);\n // Determine which video URLs to use (iOS alpha support)\n const useAlphaMethod = (isIOS && hotspot.useIOSVideoAlphaMethod) || hotspot.forceIOSVideoAlphaMethodForAllDevices;\n const mainVideoUrl = useAlphaMethod && hotspot.iosMainVideoUrl ? hotspot.iosMainVideoUrl : hotspot.videoUrl;\n const alphaVideoUrl = useAlphaMethod ? (hotspot.alphaMaskVideoUrl || null) : null;\n // Detect if URL is a WebM file (which may contain alpha)\n const isWebMUrl = (url: string): boolean => {\n const lower = url.toLowerCase();\n return lower.endsWith('.webm') || lower.includes('format=webm') || lower.includes('video/webm');\n };\n // Check if this video is WebM (may have embedded alpha)\n const mainIsWebM = isWebMUrl(mainVideoUrl);\n const hasWebMAlpha = mainIsWebM && (hotspot.webmHasAlpha !== false); // Default true for WebM\n // Helper to create video element and texture\n const createVideoAndTexture = (url: string, isAlpha: boolean, useRGBA: boolean = false) => {\n const v = document.createElement('video');\n v.src = url;\n v.loop = hotspot.videoLoop !== false;\n v.crossOrigin = 'anonymous';\n v.playsInline = true;\n // Default muted state\n if (isAlpha) {\n v.muted = true; // Alpha video always muted\n }\n else {\n v.muted = hotspot.videoMuted !== false;\n }\n // Autoplay handling\n if (hotspot.mediaTriggerMode === 'autoplay') {\n v.autoplay = true;\n v.muted = true; // Autoplay requires muted (browser policy)\n }\n // Use RGBA format for WebM (alpha support) or standard RGB\n const t = new pc.Texture(app.graphicsDevice, {\n format: useRGBA ? pc.PIXELFORMAT_R8_G8_B8_A8 : pc.PIXELFORMAT_R8_G8_B8,\n mipmaps: false,\n minFilter: pc.FILTER_LINEAR,\n magFilter: pc.FILTER_LINEAR,\n addressU: pc.ADDRESS_CLAMP_TO_EDGE,\n addressV: pc.ADDRESS_CLAMP_TO_EDGE\n });\n t.setSource(v);\n return { video: v, texture: t };\n };\n // Create main video and texture - use RGBA for WebM to support embedded alpha\n const main = createVideoAndTexture(mainVideoUrl, false, hasWebMAlpha);\n const video = main.video;\n const videoTexture = main.texture;\n // Add fallback to backup video URL if main fails (codec compatibility)\n if (hotspot.videoBackupUrl) {\n const backupUrl = hotspot.videoBackupUrl;\n video.onerror = () => {\n console.log(`[Hotspot] Main video failed, trying backup: ${backupUrl}`);\n video.src = backupUrl;\n video.load();\n };\n }\n // Create material - matching HTML export exactly\n const material = new pc.StandardMaterial();\n // Always use emissive for self-illumination (matches HTML export)\n material.diffuseMap = videoTexture;\n material.emissiveMap = videoTexture;\n material.emissive = new pc.Color(1, 1, 1);\n // Enable depth testing so hotspots are occluded by splats\n // Use depthWrite=true with premultiplied alpha for proper GSplat depth sorting\n material.depthTest = true;\n material.depthWrite = true;\n material.cull = pc.CULLFACE_NONE; // Double-sided rendering\n material.twoSidedLighting = true; // Proper lighting on both sides\n // Default blend type for non-transparent videos\n material.blendType = pc.BLEND_PREMULTIPLIED;\n material.alphaTest = 0.01;\n // WebM with embedded alpha - configure for transparency\n if (hasWebMAlpha) {\n material.opacityMap = videoTexture; // Alpha channel from same texture\n material.blendType = pc.BLEND_PREMULTIPLIED;\n material.alphaTest = 0.01;\n console.log(`[Hotspot] WebM video with alpha enabled: ${hotspot.title}`);\n }\n // Alpha video support (iOS dual-video method) - takes precedence over WebM embedded alpha\n // iOS uses a separate grayscale video where luminance = alpha (chroma key technique)\n let alphaVideo: HTMLVideoElement | null = null;\n let alphaTexture: pc.Texture | null = null;\n if (alphaVideoUrl) {\n const alpha = createVideoAndTexture(alphaVideoUrl, true, false);\n alphaVideo = alpha.video;\n alphaTexture = alpha.texture;\n material.opacityMap = alphaTexture;\n // CRITICAL: Use 'r' channel for opacity since iOS alpha videos store\n // alpha as grayscale luminance in RGB, not in actual alpha channel\n // This matches HTML export's getAlphaFromRGB = true behavior\n material.opacityMapChannel = 'r';\n material.blendType = pc.BLEND_PREMULTIPLIED;\n material.alphaTest = 0.01;\n // Synchronize alpha video with main video\n video.addEventListener('play', () => {\n if (alphaVideo && alphaVideo.paused) {\n alphaVideo.currentTime = video.currentTime;\n alphaVideo.play().catch(console.warn);\n }\n });\n video.addEventListener('pause', () => {\n if (alphaVideo && !alphaVideo.paused) {\n alphaVideo.pause();\n }\n });\n video.addEventListener('seeked', () => {\n if (alphaVideo) {\n alphaVideo.currentTime = video.currentTime;\n }\n });\n console.log(`[Hotspot] iOS alpha mask video enabled: ${hotspot.title}, alphaUrl=${alphaVideoUrl.substring(0, 50)}...`);\n }\n material.update();\n // Update textures each frame\n app.on('update', () => {\n if (video.readyState === video.HAVE_ENOUGH_DATA) {\n videoTexture.upload();\n }\n if (alphaVideo && alphaTexture && alphaVideo.readyState === alphaVideo.HAVE_ENOUGH_DATA) {\n alphaTexture.upload();\n }\n });\n // Aspect ratio scaling from video metadata\n const initialScale = { x: sx, y: sy, z: sz };\n video.addEventListener('loadedmetadata', () => {\n const width = video.videoWidth;\n const height = video.videoHeight;\n if (width > 0 && height > 0) {\n const ratio = width / height;\n // Only auto-adjust if scale is default (1,1)\n if (initialScale.x === 1 && initialScale.y === 1) {\n entity.setLocalScale(ratio * initialScale.y, initialScale.y, initialScale.z);\n }\n }\n });\n entity.render!.material = material;\n entity.setLocalScale(sx, sy, sz);\n // Rotate plane to face forward (upright)\n entity.rotateLocal(90, 0, 0);\n // Store video references for playback control\n (entity as ViewerRuntimeEntity).videoElement = video;\n (entity as ViewerRuntimeEntity).alphaVideoElement = alphaVideo;\n (entity as ViewerRuntimeEntity).hotspotMaterial = material;\n // Store media trigger mode and proximity settings\n (entity as ViewerRuntimeEntity).mediaTriggerMode = hotspot.mediaTriggerMode || 'click';\n (entity as ViewerRuntimeEntity).proximityDistance = hotspot.proximityDistance || 5;\n (entity as ViewerRuntimeEntity).isVideoPlaying = false;\n // Setup spatial audio for video if not muted\n if (hotspot.videoMuted !== true) {\n const videoSpatialAudio = setupVideoSpatialAudio(entity, video, hotspot);\n if (videoSpatialAudio) {\n (entity as ViewerRuntimeEntity).videoSpatialAudio = videoSpatialAudio;\n }\n }\n console.log(`[Hotspot] Created video hotspot: ${hotspot.title}, mode=${hotspot.mediaTriggerMode}, useAlpha=${!!alphaVideoUrl}, spatialAudio=${!!(entity as ViewerRuntimeEntity).videoSpatialAudio}`);\n }\n else if (hotspot.type === 'gif' && hotspot.gifUrl) {\n // GIF hotspot implementation - animated texture using canvas\n entity.addComponent('render', {\n type: 'plane',\n castShadows: false,\n receiveShadows: false\n });\n // Get initial opacity\n let initialOpacity = 1;\n if (hotspot.opacityMode === 'animated' && hotspot.opacityAnimation) {\n initialOpacity = hotspot.opacityAnimation.startOpacity !== undefined\n ? hotspot.opacityAnimation.startOpacity\n : 1;\n }\n else if (hotspot.opacity !== undefined) {\n initialOpacity = hotspot.opacity;\n }\n // Store state for GIF animation\n (entity as ViewerRuntimeEntity).targetOpacity = initialOpacity;\n (entity as ViewerRuntimeEntity).textureLoaded = false;\n (entity as ViewerRuntimeEntity).hiddenUntilTextureLoaded = true;\n entity.enabled = false;\n // Check useLighting setting from hotspot (default to false = unlit)\n const useLighting = hotspot.useLighting === true;\n // Create material for GIF\n // Use BLEND_PREMULTIPLIED with depthWrite=true for proper GSplat depth sorting\n const material = new pc.StandardMaterial();\n material.blendType = pc.BLEND_PREMULTIPLIED;\n material.opacity = initialOpacity;\n material.depthTest = true;\n material.depthWrite = true;\n material.cull = pc.CULLFACE_NONE;\n material.twoSidedLighting = true;\n material.alphaTest = 0.01;\n if (!useLighting) {\n material.emissive = new pc.Color(1, 1, 1);\n material.diffuse = new pc.Color(0, 0, 0);\n }\n // Load GIF using SuperGif library pattern (parse frames)\n const loadAnimatedGif = async (gifUrl: string) => {\n try {\n // Create a canvas for GIF rendering\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d')!;\n // Load GIF as image first to get dimensions\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => {\n canvas.width = img.width || 256;\n canvas.height = img.height || 256;\n // Draw static frame initially\n ctx.drawImage(img, 0, 0);\n // Create texture from canvas\n const texture = new pc.Texture(app.graphicsDevice, {\n format: pc.PIXELFORMAT_R8_G8_B8_A8,\n mipmaps: false,\n minFilter: pc.FILTER_LINEAR,\n magFilter: pc.FILTER_LINEAR,\n addressU: pc.ADDRESS_CLAMP_TO_EDGE,\n addressV: pc.ADDRESS_CLAMP_TO_EDGE\n });\n texture.setSource(canvas);\n material.diffuseMap = texture;\n if (!useLighting) {\n material.emissiveMap = texture;\n }\n material.opacityMap = texture;\n material.alphaTest = 0.01;\n material.update();\n entity.render!.material = material;\n // Calculate aspect ratio\n const ratio = canvas.width / canvas.height;\n if (sx === 1 && sy === 1) {\n entity.setLocalScale(ratio, 1, sz);\n }\n else {\n entity.setLocalScale(sx, sy, sz);\n }\n // Rotate plane to face forward\n entity.rotateLocal(90, 0, 0);\n // Mark texture as loaded\n (entity as ViewerRuntimeEntity).textureLoaded = true;\n (entity as ViewerRuntimeEntity).gifCanvas = canvas;\n (entity as ViewerRuntimeEntity).gifTexture = texture;\n (entity as ViewerRuntimeEntity).hotspotMaterial = material;\n // Enable entity if not hidden by visibility range\n if (!(entity as ViewerRuntimeEntity).visibilityRange || (entity as ViewerRuntimeEntity).shouldBeVisible) {\n entity.enabled = true;\n }\n (entity as ViewerRuntimeEntity).hiddenUntilTextureLoaded = false;\n // Try to load and animate the GIF frames using fetch + gif parsing\n fetch(gifUrl)\n .then(response => response.arrayBuffer())\n .then(buffer => {\n // Simple GIF frame extraction (will show animated if browser supports it via img tag)\n // For full animation support, we continuously redraw the img element\n let frameIndex = 0;\n const animateGif = () => {\n if (!entity.enabled || !(entity as ViewerRuntimeEntity).gifTexture)\n return;\n // Redraw from the img which browsers animate automatically\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.drawImage(img, 0, 0);\n (entity as ViewerRuntimeEntity).gifTexture?.upload();\n // Continue animation loop\n requestAnimationFrame(animateGif);\n };\n // Start animation loop\n (entity as ViewerRuntimeEntity).gifAnimationFrame = requestAnimationFrame(animateGif);\n })\n .catch(err => {\n console.warn('[Hotspot] GIF animation load failed, using static frame:', err);\n });\n console.log(`[Hotspot] Created GIF hotspot: ${hotspot.title}, opacity=${initialOpacity}, useLighting=${useLighting}`);\n };\n img.onerror = (err) => {\n console.error('[Hotspot] Failed to load GIF:', hotspot.gifUrl, err);\n // Fallback to sphere\n entity.addComponent('render', {\n type: 'sphere',\n castShadows: false,\n receiveShadows: false\n });\n const fallbackMaterial = new pc.StandardMaterial();\n fallbackMaterial.diffuse = parseColor(hotspot.color || '#FF00FF');\n fallbackMaterial.opacity = 0.8;\n fallbackMaterial.blendType = pc.BLEND_NORMAL;\n fallbackMaterial.update();\n entity.render!.material = fallbackMaterial;\n entity.setLocalScale(sx * 0.2, sy * 0.2, sz * 0.2);\n entity.enabled = true;\n (entity as ViewerRuntimeEntity).hiddenUntilTextureLoaded = false;\n };\n img.src = gifUrl;\n }\n catch (err) {\n console.error('[Hotspot] GIF hotspot creation failed:', err);\n }\n };\n loadAnimatedGif(hotspot.gifUrl);\n }\n else {\n // Default: sphere\n entity.addComponent('render', {\n type: 'sphere',\n castShadows: false,\n receiveShadows: false\n });\n const material = new pc.StandardMaterial();\n const color = parseColor(hotspot.color || '#4CAF50');\n material.diffuse = color;\n material.emissive = color.clone();\n (material.emissive as pc.Color).mulScalar(0.5);\n // Use additive blending for better GSplat compatibility\n const sphereOpacity = hotspot.opacity ?? 0.8;\n material.opacity = sphereOpacity;\n material.blendType = pc.BLEND_ADDITIVEALPHA;\n material.depthTest = true;\n material.depthWrite = true;\n material.update();\n entity.render!.material = material;\n entity.setLocalScale(sx * 0.2, sy * 0.2, sz * 0.2);\n }\n // Add collision for raycasting\n entity.addComponent('collision', {\n type: hotspot.type === 'sphere' ? 'sphere' : 'box',\n radius: 0.1,\n halfExtents: new pc.Vec3(0.5, 0.5, 0.05)\n });\n // Store hotspot data for click handling\n (entity as ViewerRuntimeEntity).hotspotData = hotspot as HotspotData;\n // Setup hotspot audio (spatial or non-spatial)\n const audioElements = setupHotspotAudio(entity, hotspot);\n if (audioElements) {\n (entity as ViewerRuntimeEntity).audioElements = audioElements;\n console.log(`[StorySplat Viewer] Audio setup for hotspot: ${hotspot.title || 'Untitled'}`);\n }\n // Billboard mode - make plane always face the camera (with optional range support)\n if (hotspot.billboard) {\n // Store original rotation for when billboard is disabled\n const originalRotation = entity.getEulerAngles().clone();\n // Check if billboard range is specified\n const hasBillboardRange = hotspot.billboardRangeStart !== undefined || hotspot.billboardRangeEnd !== undefined;\n // Initialize billboard active state\n (entity as ViewerRuntimeEntity)._billboardActive = !hasBillboardRange; // Active by default if no range\n (entity as ViewerRuntimeEntity)._billboardOriginalRotation = originalRotation;\n app.on('update', () => {\n // Only apply billboard if active (no range or within range)\n if ((entity as ViewerRuntimeEntity)._billboardActive) {\n entity.lookAt(camera.getPosition());\n // Rotate plane upright after lookAt\n entity.rotateLocal(90, 0, 0);\n }\n });\n }\n // Visibility range\n if (hotspot.visibilityRange) {\n (entity as ViewerRuntimeEntity).visibilityRange = hotspot.visibilityRange;\n entity.enabled = false; // Start hidden\n }\n app.root.addChild(entity);\n hotspotEntities.push(entity);\n console.log(`[StorySplat Viewer] Created hotspot: ${hotspot.title || 'Untitled'}`);\n return entity;\n }\n // Create all hotspots from config\n function createHotspots(): void {\n if (!config.hotspots || config.hotspots.length === 0) {\n console.log('[StorySplat Viewer] No hotspots to create');\n return;\n }\n console.log(`[StorySplat Viewer] Creating ${config.hotspots.length} hotspots...`);\n config.hotspots.forEach((hotspot, index: number) => {\n createSingleHotspot(hotspot, index);\n });\n }\n // Update hotspot visibility, opacity, and video playback based on progress\n function updateHotspotVisibility(): void {\n const scrollPercent = currentProgress * 100;\n const numWaypoints = config.waypoints?.length || 1;\n const waypointIndex = Math.round(currentProgress * Math.max(1, numWaypoints - 1));\n const cameraPos = camera.getPosition();\n hotspotEntities.forEach((entity) => {\n const hotspot = entity.hotspotData;\n if (!hotspot)\n return;\n // Determine if hotspot should be visible based on visibility range\n // alwaysVisible overrides visibility range\n let shouldBeVisible = true;\n if (hotspot.alwaysVisible) {\n shouldBeVisible = true;\n }\n else {\n const range = entity.visibilityRange;\n if (range) {\n if (range.type === 'waypoint') {\n shouldBeVisible = waypointIndex >= range.start && waypointIndex <= range.end;\n }\n else if (range.type === 'percentage') {\n shouldBeVisible = scrollPercent >= range.start && scrollPercent <= range.end;\n }\n }\n }\n // Store visibility state for texture load callback\n entity.shouldBeVisible = shouldBeVisible;\n // Update billboard state based on range (if billboard mode is enabled with range)\n if (hotspot.billboard && (hotspot.billboardRangeStart !== undefined || hotspot.billboardRangeEnd !== undefined)) {\n const rangeStart = hotspot.billboardRangeStart ?? 0;\n const rangeEnd = hotspot.billboardRangeEnd ?? 100;\n const billboardActive = scrollPercent >= rangeStart && scrollPercent <= rangeEnd;\n // Update billboard active state\n entity._billboardActive = billboardActive;\n // If billboard is deactivated and we have original rotation, restore it\n if (!billboardActive && entity._billboardOriginalRotation) {\n const origRot = entity._billboardOriginalRotation;\n entity.setEulerAngles(origRot.x, origRot.y, origRot.z);\n }\n }\n // Only enable if: should be visible AND (not an image OR texture is loaded)\n if (entity.hiddenUntilTextureLoaded) {\n // Image hotspot waiting for texture - keep hidden\n entity.enabled = false;\n }\n else {\n entity.enabled = shouldBeVisible;\n }\n // Handle video playback triggers\n if (entity.videoElement && hotspot.type === 'video') {\n const triggerMode = entity.mediaTriggerMode || 'click';\n // Proximity trigger: play when camera is within proximityDistance\n if (triggerMode === 'proximity') {\n const hotspotPos = entity.getPosition();\n const distance = cameraPos.distance(hotspotPos);\n const proximityDistance = entity.proximityDistance || 5;\n if (distance <= proximityDistance && !entity.isVideoPlaying) {\n // Enter proximity - play video\n playVideoHotspot(entity, hotspot);\n console.log(`[Hotspot] Proximity play: ${hotspot.title}, distance=${distance.toFixed(2)}`);\n }\n else if (distance > proximityDistance && entity.isVideoPlaying) {\n // Leave proximity - pause video\n pauseVideoHotspot(entity);\n console.log(`[Hotspot] Proximity pause: ${hotspot.title}, distance=${distance.toFixed(2)}`);\n }\n }\n // Scroll trigger: play when visible based on visibility range\n if (triggerMode === 'scroll') {\n if (shouldBeVisible && !entity.isVideoPlaying) {\n // Entered visibility range - play video\n playVideoHotspot(entity, hotspot);\n console.log(`[Hotspot] Scroll play: ${hotspot.title}, scroll=${scrollPercent.toFixed(1)}%`);\n }\n else if (!shouldBeVisible && entity.isVideoPlaying) {\n // Left visibility range - pause video\n pauseVideoHotspot(entity);\n console.log(`[Hotspot] Scroll pause: ${hotspot.title}, scroll=${scrollPercent.toFixed(1)}%`);\n }\n }\n }\n // Handle opacity animation (only if texture is loaded for image hotspots)\n if (hotspot.opacityMode === 'animated' && hotspot.opacityAnimation) {\n // Skip if this is an image hotspot and texture isn't loaded yet\n if (hotspot.type === 'image' && !entity.textureLoaded) {\n return;\n }\n const anim = hotspot.opacityAnimation;\n const startPercent = anim.startPercent ?? 0;\n const endPercent = anim.endPercent ?? 100;\n // Default to 1 (visible) if not explicitly set\n const startOpacity = anim.startOpacity !== undefined ? anim.startOpacity : 1;\n const endOpacity = anim.endOpacity !== undefined ? anim.endOpacity : 1;\n let opacity: number;\n if (scrollPercent <= startPercent) {\n opacity = startOpacity;\n }\n else if (scrollPercent >= endPercent) {\n opacity = endOpacity;\n }\n else {\n // Linear interpolation\n const animRange = endPercent - startPercent;\n const progress = (scrollPercent - startPercent) / animRange;\n opacity = startOpacity + (endOpacity - startOpacity) * progress;\n }\n opacity = Math.max(0, Math.min(1, opacity));\n // Update material opacity\n if (entity.hotspotMaterial) {\n entity.hotspotMaterial.opacity = opacity;\n entity.hotspotMaterial.update();\n }\n else if (entity.render && entity.render.material) {\n // Fallback to render material\n const material = entity.render.material as pc.Material & {\n opacity?: number;\n };\n material.opacity = opacity;\n material.update?.();\n }\n }\n });\n }\n // Helper to play/pause video (defined here so updateHotspotVisibility can use them)\n function playVideoHotspot(entity: ViewerRuntimeEntity, hotspot: HotspotConfigLike): void {\n const video = entity.videoElement as HTMLVideoElement;\n const alphaVideo = entity.alphaVideoElement as HTMLVideoElement | null;\n if (!video)\n return;\n // Set muted state based on hotspot settings (except for autoplay which is always muted initially)\n if (entity.mediaTriggerMode !== 'autoplay') {\n video.muted = hotspot.videoMuted !== false;\n }\n // Resume AudioContext for spatial audio (browser requires user interaction)\n if (entity.videoSpatialAudio && entity.videoSpatialAudio.audioCtx) {\n const audioCtx = entity.videoSpatialAudio.audioCtx as AudioContext;\n if (audioCtx.state === 'suspended') {\n audioCtx.resume().then(() => {\n console.log('[Audio] Video spatial audio context resumed');\n }).catch(err => console.warn('[Audio] Failed to resume video audio context:', err));\n }\n }\n video.play().catch(err => console.warn('Video play failed:', err));\n if (alphaVideo) {\n alphaVideo.play().catch(err => console.warn('Alpha video play failed:', err));\n }\n entity.isVideoPlaying = true;\n }\n function pauseVideoHotspot(entity: ViewerRuntimeEntity): void {\n const video = entity.videoElement as HTMLVideoElement;\n const alphaVideo = entity.alphaVideoElement as HTMLVideoElement | null;\n if (!video)\n return;\n video.pause();\n if (alphaVideo) {\n alphaVideo.pause();\n }\n entity.isVideoPlaying = false;\n }\n // Listen for progress updates to update hotspot visibility\n events.on('progressUpdate', () => {\n updateHotspotVisibility();\n });\n // Also call initially after hotspots are created\n setTimeout(() => {\n updateHotspotVisibility();\n }, 100);\n // =====================================================\n // PORTAL CREATION AND RENDERING\n // =====================================================\n // Create a single portal entity with full rendering (extracted for editor reuse)\n function createSinglePortal(portal: PortalConfigLike, index: number): pc.Entity {\n const entity = new pc.Entity(`portal-${index}`);\n // Get position (negate Z for BabylonJS -> PlayCanvas coordinate conversion)\n const pos = portal.position || { _x: 0, _y: 0, _z: 0 };\n entity.setPosition(pos._x ?? pos.x ?? 0, pos._y ?? pos.y ?? 0, -(pos._z ?? pos.z ?? 0));\n // Get scale\n const rawPortalScale = portal.scale || { _x: 1, _y: 1, _z: 1 };\n const portalScaleObj = typeof rawPortalScale === 'number' ? { x: rawPortalScale, y: rawPortalScale, z: rawPortalScale } as LegacyVec3 : rawPortalScale;\n const sx = Math.abs(portalScaleObj._x ?? portalScaleObj.x ?? 1);\n const sy = Math.abs(portalScaleObj._y ?? portalScaleObj.y ?? 1);\n const sz = portalScaleObj._z ?? portalScaleObj.z ?? 1;\n // Get rotation (convert from radians to degrees, match BabylonJS -> PlayCanvas)\n const rot = portal.rotation || { _x: 0, _y: 0, _z: 0 };\n const radToDeg = 180 / Math.PI;\n const rotX = (rot._x ?? rot.x ?? 0) * radToDeg;\n const rotY = (rot._y ?? rot.y ?? 0) * radToDeg;\n const rotZ = (rot._z ?? rot.z ?? 0) * radToDeg;\n entity.setEulerAngles(rotX, rotY, -rotZ);\n // Create geometry based on type (similar to hotspots)\n if (portal.type === 'sphere') {\n entity.addComponent('render', {\n type: 'sphere',\n castShadows: false,\n receiveShadows: false\n });\n const material = new pc.StandardMaterial();\n const color = parseColor(portal.color || '#9C27B0'); // Purple default for portals\n material.diffuse = color;\n material.emissive = color.clone();\n (material.emissive as pc.Color).mulScalar(0.5);\n // For spheres: use full opacity with depth write for proper GSplat occlusion\n const sphereOpacity = portal.opacity ?? 0.8;\n if (sphereOpacity >= 0.95) {\n material.opacity = 1;\n material.blendType = pc.BLEND_NONE;\n material.depthTest = true;\n material.depthWrite = true;\n }\n else {\n material.opacity = sphereOpacity;\n material.blendType = pc.BLEND_ADDITIVEALPHA;\n material.depthTest = true;\n material.depthWrite = true;\n }\n material.update();\n entity.render!.material = material;\n entity.setLocalScale(sx * 0.2, sy * 0.2, sz * 0.2);\n }\n else if (portal.type === 'image' && portal.imageUrl) {\n entity.addComponent('render', {\n type: 'plane',\n castShadows: false,\n receiveShadows: false\n });\n const useLighting = portal.useLighting === true;\n const initialOpacity = portal.opacity ?? 1;\n (entity as ViewerRuntimeEntity).textureLoaded = false;\n (entity as ViewerRuntimeEntity).hiddenUntilTextureLoaded = true;\n entity.enabled = false;\n const material = createImageMaterial(portal.imageUrl, useLighting, initialOpacity, () => {\n (entity as ViewerRuntimeEntity).textureLoaded = true;\n if (!(entity as ViewerRuntimeEntity).visibilityRange || (entity as ViewerRuntimeEntity).shouldBeVisible) {\n entity.enabled = true;\n }\n (entity as ViewerRuntimeEntity).hiddenUntilTextureLoaded = false;\n });\n entity.render!.material = material;\n entity.setLocalScale(sx, sy, sz);\n entity.rotateLocal(90, 0, 0);\n (entity as ViewerRuntimeEntity).portalMaterial = material;\n console.log(`[Portal] Created image portal: ${portal.title || portal.targetSceneName || 'Untitled'}`);\n }\n else if (portal.type === 'video' && portal.videoUrl) {\n // Video portal\n entity.addComponent('render', {\n type: 'plane',\n castShadows: false,\n receiveShadows: false\n });\n const video = document.createElement('video');\n video.src = portal.videoUrl;\n video.loop = true;\n video.muted = true;\n video.crossOrigin = 'anonymous';\n video.playsInline = true;\n video.autoplay = true;\n const videoTexture = new pc.Texture(app.graphicsDevice, {\n format: pc.PIXELFORMAT_R8_G8_B8,\n mipmaps: false,\n minFilter: pc.FILTER_LINEAR,\n magFilter: pc.FILTER_LINEAR,\n addressU: pc.ADDRESS_CLAMP_TO_EDGE,\n addressV: pc.ADDRESS_CLAMP_TO_EDGE\n });\n videoTexture.setSource(video);\n const material = new pc.StandardMaterial();\n material.diffuseMap = videoTexture;\n material.emissiveMap = videoTexture;\n material.emissive = new pc.Color(1, 1, 1);\n material.depthTest = true;\n material.depthWrite = true;\n material.cull = pc.CULLFACE_NONE;\n material.blendType = pc.BLEND_PREMULTIPLIED;\n material.opacity = portal.opacity ?? 1;\n material.update();\n entity.render!.material = material;\n entity.setLocalScale(sx, sy, sz);\n entity.rotateLocal(90, 0, 0);\n // Update video texture every frame\n app.on('update', () => {\n if (video.readyState >= video.HAVE_CURRENT_DATA) {\n videoTexture.setSource(video);\n }\n });\n // Start playing when visible\n video.play().catch(e => console.log('[Portal] Video autoplay blocked:', e));\n (entity as ViewerRuntimeEntity).videoElement = video;\n (entity as ViewerRuntimeEntity).portalMaterial = material;\n console.log(`[Portal] Created video portal: ${portal.title || portal.targetSceneName || 'Untitled'}`);\n }\n else if (portal.type === 'gif' && portal.gifUrl) {\n // GIF portal - use AnimatedGifTexture if available, otherwise static image\n entity.addComponent('render', {\n type: 'plane',\n castShadows: false,\n receiveShadows: false\n });\n const useLighting = portal.useLighting === true;\n const initialOpacity = portal.opacity ?? 1;\n // Try to use animated GIF texture, fallback to static image\n const material = createImageMaterial(portal.gifUrl, useLighting, initialOpacity, () => {\n (entity as ViewerRuntimeEntity).textureLoaded = true;\n if (!(entity as ViewerRuntimeEntity).visibilityRange || (entity as ViewerRuntimeEntity).shouldBeVisible) {\n entity.enabled = true;\n }\n (entity as ViewerRuntimeEntity).hiddenUntilTextureLoaded = false;\n });\n entity.render!.material = material;\n entity.setLocalScale(sx, sy, sz);\n entity.rotateLocal(90, 0, 0);\n (entity as ViewerRuntimeEntity).textureLoaded = false;\n (entity as ViewerRuntimeEntity).hiddenUntilTextureLoaded = true;\n entity.enabled = false;\n (entity as ViewerRuntimeEntity).portalMaterial = material;\n console.log(`[Portal] Created GIF portal: ${portal.title || portal.targetSceneName || 'Untitled'}`);\n }\n else {\n // Default: sphere\n entity.addComponent('render', {\n type: 'sphere',\n castShadows: false,\n receiveShadows: false\n });\n const material = new pc.StandardMaterial();\n const color = parseColor(portal.color || '#9C27B0');\n material.diffuse = color;\n material.emissive = color.clone();\n (material.emissive as pc.Color).mulScalar(0.5);\n // Use additive blending for better GSplat compatibility\n const sphereOpacity = portal.opacity ?? 0.8;\n material.opacity = sphereOpacity;\n material.blendType = pc.BLEND_ADDITIVEALPHA;\n material.depthTest = true;\n material.depthWrite = true;\n material.update();\n entity.render!.material = material;\n entity.setLocalScale(sx * 0.2, sy * 0.2, sz * 0.2);\n }\n // Add collision for raycasting\n entity.addComponent('collision', {\n type: portal.type === 'sphere' ? 'sphere' : 'box',\n radius: 0.1,\n halfExtents: new pc.Vec3(0.5, 0.5, 0.05)\n });\n // Store portal data for click handling\n (entity as ViewerRuntimeEntity).portalData = portal as PortalData;\n // Billboard mode\n if (portal.billboard) {\n app.on('update', () => {\n if (entity.enabled) {\n entity.lookAt(camera.getPosition());\n entity.rotateLocal(90, 0, 0);\n }\n });\n }\n // Visibility range\n if (portal.visibilityRange) {\n (entity as ViewerRuntimeEntity).visibilityRange = portal.visibilityRange;\n entity.enabled = false;\n }\n app.root.addChild(entity);\n portalEntities.push(entity);\n console.log(`[StorySplat Viewer] Created portal: ${portal.title || portal.targetSceneName || 'Untitled'} -> ${portal.targetSceneId}`);\n return entity;\n }\n // Create all portals (similar to hotspots but for scene-to-scene navigation)\n function createPortals(): void {\n if (!config.portals || config.portals.length === 0) {\n console.log('[StorySplat Viewer] No portals to create');\n return;\n }\n console.log(`[StorySplat Viewer] Creating ${config.portals.length} portals...`);\n config.portals.forEach((portal, index: number) => {\n createSinglePortal(portal, index);\n });\n }\n // Update portal visibility based on progress\n function updatePortalVisibility(): void {\n const scrollPercent = currentProgress * 100;\n const numWaypoints = config.waypoints?.length || 1;\n const waypointIndex = Math.round(currentProgress * Math.max(1, numWaypoints - 1));\n const cameraPos = camera.getPosition();\n portalEntities.forEach((entity) => {\n const portal = entity.portalData;\n if (!portal)\n return;\n // Determine if portal should be visible based on visibility range\n let shouldBeVisible = true;\n const range = entity.visibilityRange;\n if (range) {\n if (range.type === 'waypoint') {\n shouldBeVisible = waypointIndex >= range.start && waypointIndex <= range.end;\n }\n else if (range.type === 'percentage') {\n shouldBeVisible = scrollPercent >= range.start && scrollPercent <= range.end;\n }\n }\n entity.shouldBeVisible = shouldBeVisible;\n // Enable/disable based on visibility and texture loaded state\n if (entity.hiddenUntilTextureLoaded) {\n entity.enabled = false;\n }\n else {\n entity.enabled = shouldBeVisible;\n }\n // Proximity activation check\n if (portal.activationMode === 'proximity' && shouldBeVisible) {\n const portalPos = entity.getPosition();\n const distance = cameraPos.distance(portalPos);\n const proximityDistance = portal.proximityDistance || 2;\n if (distance <= proximityDistance && !entity.proximityTriggered) {\n entity.proximityTriggered = true;\n console.log(`[Portal] Proximity triggered: ${portal.title || portal.targetSceneName}, navigating to scene ${portal.targetSceneId}`);\n handlePortalNavigation(portal);\n }\n else if (distance > proximityDistance) {\n entity.proximityTriggered = false;\n }\n }\n });\n }\n // Listen for progress updates to update portal visibility\n events.on('progressUpdate', () => {\n updatePortalVisibility();\n });\n // Also call initially after portals are created\n setTimeout(() => {\n updatePortalVisibility();\n }, 100);\n // Raycast helper for portal detection\n function raycastPortals(x: number, y: number): {\n entity: ViewerRuntimeEntity;\n portal: PortalConfigLike;\n } | null {\n const from = camera.camera!.screenToWorld(x, y, camera.camera!.nearClip);\n const to = camera.camera!.screenToWorld(x, y, camera.camera!.farClip);\n let closestHit: {\n entity: ViewerRuntimeEntity;\n distance: number;\n } | null = null;\n portalEntities.forEach(entity => {\n if (!entity.enabled)\n return;\n const pos = entity.getPosition();\n const dir = new pc.Vec3().sub2(to, from).normalize();\n const toEntity = new pc.Vec3().sub2(pos, from);\n const t = toEntity.dot(dir);\n if (t < 0)\n return;\n const closestPoint = new pc.Vec3().add2(from, dir.clone().mulScalar(t));\n const distance = closestPoint.distance(pos);\n const entityScale = entity.getLocalScale();\n const hitRadius = Math.max(entityScale.x, entityScale.y, 0.3) * 0.6;\n if (distance < hitRadius) {\n if (!closestHit || t < closestHit.distance) {\n closestHit = { entity, distance: t };\n }\n }\n });\n // TypeScript doesn't track assignments inside forEach callbacks\n const portalHitResult = closestHit as { entity: ViewerRuntimeEntity; distance: number } | null;\n if (portalHitResult !== null) {\n const entity = portalHitResult.entity;\n return { entity, portal: entity.portalData! as PortalConfigLike };\n }\n return null;\n }\n // Handle portal navigation (scene-to-scene)\n async function handlePortalNavigation(portal: PortalConfigLike): Promise<void> {\n if (!portal.targetSceneId) {\n console.warn('[Portal] No target scene ID specified');\n return;\n }\n console.log(`[StorySplat Viewer] Portal activated: navigating to scene ${portal.targetSceneId}`);\n // Emit event for external handlers\n events.emit('portalActivated', {\n portalId: portal.id,\n targetSceneId: portal.targetSceneId,\n targetSceneName: portal.targetSceneName\n });\n // Show loading UI - use pretty name if available, otherwise generic text\n showPortalLoadingUI(container, portal.targetSceneName || portal.title || 'scene');\n try {\n // Fetch the new scene data\n const sceneApiUrl = `https://discover.storysplat.com/api/scene/${portal.targetSceneId}`;\n console.log(`[Portal] Fetching scene from: ${sceneApiUrl}`);\n const response = await fetch(sceneApiUrl);\n if (!response.ok) {\n throw new Error(`Failed to fetch scene: ${response.status} ${response.statusText}`);\n }\n const result = await response.json();\n const newSceneData = result.data || result;\n // Update loading UI with the actual scene name from the API\n if (newSceneData.name) {\n updatePortalLoadingText(container, newSceneData.name);\n }\n // Store current instance reference before cleanup\n const currentInstance = instance;\n // Cleanup current scene (iOS memory management)\n cleanupForPortalNavigation();\n // Small delay for garbage collection (iOS)\n await new Promise(resolve => setTimeout(resolve, 100));\n // Create new viewer with the fetched scene data\n // Note: We're recreating in the same container, so we need to remove the current canvas first\n if (canvas && canvas.parentNode) {\n canvas.remove();\n }\n // Create new viewer (this will replace 'instance' reference for external code)\n const newViewer = await createViewer(container, newSceneData, {});\n // Hide loading UI\n hidePortalLoadingUI(container);\n console.log(`[Portal] Successfully navigated to scene: ${portal.targetSceneId}`);\n // Return the new viewer instance (for programmatic use)\n return;\n }\n catch (error) {\n console.error('[Portal] Navigation failed:', error);\n hidePortalLoadingUI(container);\n // Show error message\n const errorDiv = document.createElement('div');\n errorDiv.className = 'storysplat-portal-error';\n errorDiv.textContent = `Failed to load scene: ${(error as Error).message}`;\n Object.assign(errorDiv.style, {\n // Keep errors contained to the viewer container (important for embeds)\n position: 'absolute',\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n background: 'rgba(0,0,0,0.9)',\n color: '#ff6b6b',\n padding: '20px',\n borderRadius: '8px',\n zIndex: '100004',\n fontFamily: 'system-ui, sans-serif'\n });\n container.appendChild(errorDiv);\n setTimeout(() => errorDiv.remove(), 3000);\n }\n }\n // Cleanup for portal navigation (memory management)\n function cleanupForPortalNavigation(): void {\n console.log('[Portal] Cleaning up current scene for navigation...');\n // Stop playback\n pause();\n // Stop all hotspot videos\n hotspotEntities.forEach((entity) => {\n if (entity.videoElement) {\n entity.videoElement.pause();\n entity.videoElement.src = '';\n }\n if (entity.alphaVideoElement) {\n entity.alphaVideoElement.pause();\n entity.alphaVideoElement.src = '';\n }\n });\n // Cleanup animated GIFs\n animatedGifs.forEach(gif => gif.destroy());\n animatedGifs.length = 0;\n // Cleanup custom meshes\n cleanupCustomMeshes();\n // Destroy hotspot entities\n hotspotEntities.forEach(entity => {\n entity.destroy();\n });\n hotspotEntities.length = 0;\n // Destroy portal entities\n portalEntities.forEach(entity => {\n entity.destroy();\n });\n portalEntities.length = 0;\n // Destroy splat entity\n if (splatEntity) {\n splatEntity.destroy();\n splatEntity = null;\n }\n // Cleanup HTML Mesh Manager\n const portalHtmlMgr = (app as ViewerRuntimeApp).__htmlMeshManager;\n if (portalHtmlMgr) {\n portalHtmlMgr.destroy();\n }\n // Cleanup Custom Script System\n const portalScriptSys = (app as ViewerRuntimeApp).__customScriptSystem;\n if (portalScriptSys) {\n portalScriptSys.dispose();\n }\n console.log('[Portal] Cleanup complete');\n }\n // Show portal loading UI\n function showPortalLoadingUI(container: HTMLElement, sceneName: string): void {\n // Remove any existing loading UI\n const existing = container.querySelector('.storysplat-portal-loading');\n if (existing)\n existing.remove();\n // Ensure the overlay is positioned relative to the viewer container\n try {\n const pos = window.getComputedStyle(container).position;\n if (pos === 'static')\n container.style.position = 'relative';\n }\n catch {\n // Best-effort only\n }\n const loadingDiv = document.createElement('div');\n loadingDiv.className = 'storysplat-portal-loading';\n const spinner = document.createElement('div');\n spinner.className = 'storysplat-portal-spinner';\n const text = document.createElement('div');\n text.className = 'storysplat-portal-loading-text';\n text.textContent = getButtonLabel(currentButtonLabels, 'loadingScene').replace('{name}', sceneName);\n loadingDiv.appendChild(spinner);\n loadingDiv.appendChild(text);\n // Styles - use scene's uiColor for the spinner\n Object.assign(loadingDiv.style, {\n position: 'absolute',\n inset: '0',\n background: 'rgba(0, 0, 0, 0.85)',\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: '100003',\n fontFamily: 'system-ui, sans-serif'\n });\n Object.assign(spinner.style, {\n width: '50px',\n height: '50px',\n border: '4px solid rgba(255, 255, 255, 0.2)',\n borderTop: `4px solid ${uiColor}`,\n borderRadius: '50%',\n animation: 'storysplat-portal-spin 1s linear infinite',\n marginBottom: '20px'\n });\n Object.assign(text.style, {\n color: '#ffffff',\n fontSize: '18px'\n });\n // Add keyframes for spinner animation\n if (!document.getElementById('storysplat-portal-styles')) {\n const styleEl = document.createElement('style');\n styleEl.id = 'storysplat-portal-styles';\n styleEl.textContent = `\n @keyframes storysplat-portal-spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n `;\n document.head.appendChild(styleEl);\n }\n container.appendChild(loadingDiv);\n }\n // Update portal loading text with actual scene name (after API fetch)\n function updatePortalLoadingText(container: HTMLElement, sceneName: string): void {\n const textEl = container.querySelector('.storysplat-portal-loading-text');\n if (textEl) {\n textEl.textContent = getButtonLabel(currentButtonLabels, 'loadingScene').replace('{name}', sceneName);\n }\n }\n // Hide portal loading UI\n function hidePortalLoadingUI(container: HTMLElement): void {\n const loadingDiv = container.querySelector('.storysplat-portal-loading');\n if (loadingDiv) {\n loadingDiv.remove();\n }\n }\n // Raycast helper for hotspot detection\n const canvasEl = app.graphicsDevice.canvas as HTMLCanvasElement;\n // Suppress clicks when the user is dragging to look around\n let clickSuppressed = false;\n let pointerDown = false;\n let pointerStartX = 0;\n let pointerStartY = 0;\n const dragThresholdPx = 5;\n canvasEl.addEventListener('pointerdown', (e: PointerEvent) => {\n if (e.button !== 0)\n return;\n pointerDown = true;\n clickSuppressed = false;\n pointerStartX = e.clientX;\n pointerStartY = e.clientY;\n });\n canvasEl.addEventListener('pointermove', (e: PointerEvent) => {\n if (!pointerDown)\n return;\n const dx = e.clientX - pointerStartX;\n const dy = e.clientY - pointerStartY;\n if ((dx * dx + dy * dy) > (dragThresholdPx * dragThresholdPx)) {\n clickSuppressed = true;\n }\n });\n const clearPointerDown = () => {\n pointerDown = false;\n };\n canvasEl.addEventListener('pointerup', clearPointerDown);\n canvasEl.addEventListener('pointercancel', clearPointerDown);\n function raycastHotspots(x: number, y: number): {\n entity: ViewerRuntimeEntity;\n hotspot: HotspotConfigLike;\n } | null {\n const from = camera.camera!.screenToWorld(x, y, camera.camera!.nearClip);\n const to = camera.camera!.screenToWorld(x, y, camera.camera!.farClip);\n let closestHit: {\n entity: ViewerRuntimeEntity;\n distance: number;\n } | null = null;\n hotspotEntities.forEach(entity => {\n if (!entity.enabled)\n return;\n const pos = entity.getPosition();\n const dir = new pc.Vec3().sub2(to, from).normalize();\n const toEntity = new pc.Vec3().sub2(pos, from);\n const t = toEntity.dot(dir);\n if (t < 0)\n return;\n const closestPoint = new pc.Vec3().add2(from, dir.clone().mulScalar(t));\n const distance = closestPoint.distance(pos);\n // Use the entity's actual scale to determine hit radius\n // Take the maximum of X and Y scale for a reasonable hit area\n const entityScale = entity.getLocalScale();\n const hitRadius = Math.max(entityScale.x, entityScale.y, 0.3) * 0.6; // Min 0.18 units, scaled by 0.6\n if (distance < hitRadius) {\n if (!closestHit || t < closestHit.distance) {\n closestHit = { entity, distance: t };\n }\n }\n });\n // TypeScript doesn't track assignments inside forEach callbacks\n const hotspotHitResult = closestHit as { entity: ViewerRuntimeEntity; distance: number } | null;\n if (hotspotHitResult !== null) {\n const entity = hotspotHitResult.entity;\n return { entity, hotspot: entity.hotspotData! as HotspotConfigLike };\n }\n return null;\n }\n // Hover detection for cursor change and hover popups\n let currentHoverHotspot: HotspotConfigLike | null = null;\n let isMouseOverPopup = false;\n // Track when mouse is over the popup\n const popup = container.querySelector('.storysplat-hotspot-popup') as HTMLElement;\n const overlay = container.querySelector('.storysplat-hotspot-overlay') as HTMLElement;\n if (popup) {\n popup.addEventListener('mouseenter', () => {\n isMouseOverPopup = true;\n });\n popup.addEventListener('mouseleave', () => {\n isMouseOverPopup = false;\n // Close popup when mouse leaves the popup (for hover-activated hotspots)\n if (currentHoverHotspot && currentHoverHotspot.activationMode === 'hover') {\n popup.classList.remove('visible');\n if (overlay)\n overlay.classList.remove('visible');\n currentHoverHotspot = null;\n }\n });\n }\n canvasEl.addEventListener('mousemove', (e: MouseEvent) => {\n const rect = canvasEl.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n // Check portals first\n const portalHit = raycastPortals(x, y);\n if (portalHit && portalHit.portal) {\n const portal = portalHit.portal;\n // Show pointer cursor for click-activated portals\n const effectiveActivationMode = portal.activationMode || 'click';\n if (effectiveActivationMode === 'click') {\n canvasEl.style.cursor = 'pointer';\n }\n else {\n canvasEl.style.cursor = 'default';\n }\n return; // Portals take precedence over hotspots\n }\n const hit = raycastHotspots(x, y);\n if (hit && hit.hotspot) {\n const hotspot = hit.hotspot;\n // Show pointer cursor for interactive hotspots\n if (hotspot.activationMode === 'click' || hotspot.activationMode === 'hover' || hotspot.type === 'video') {\n canvasEl.style.cursor = 'pointer';\n }\n else {\n canvasEl.style.cursor = 'default';\n }\n // Show popup on hover for hover-activated hotspots\n if (hotspot.activationMode === 'hover' && currentHoverHotspot !== hotspot) {\n currentHoverHotspot = hotspot;\n if (hotspot.information || hotspot.photoUrl || hotspot.iframeUrl || hotspot.externalLinkUrl) {\n showHotspotPopup(container, hotspot as HotspotData, currentButtonLabels);\n }\n }\n }\n else {\n canvasEl.style.cursor = 'default';\n // Only hide popup if mouse is NOT over the popup element\n if (currentHoverHotspot && currentHoverHotspot.activationMode === 'hover' && !isMouseOverPopup) {\n const popupEl = container.querySelector('.storysplat-hotspot-popup') as HTMLElement;\n const overlayEl = container.querySelector('.storysplat-hotspot-overlay') as HTMLElement;\n if (popupEl)\n popupEl.classList.remove('visible');\n if (overlayEl)\n overlayEl.classList.remove('visible');\n currentHoverHotspot = null;\n }\n }\n });\n // Click handling for hotspots and portals\n canvasEl.addEventListener('click', (e: MouseEvent) => {\n if (clickSuppressed) {\n clickSuppressed = false;\n return;\n }\n const rect = canvasEl.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n // Check for portal clicks first (portals take precedence)\n const portalHit = raycastPortals(x, y);\n if (portalHit !== null && portalHit.portal) {\n const portal = portalHit.portal;\n console.log('[StorySplat Viewer] Portal clicked:', portal.title || portal.targetSceneName);\n // Handle click-activated portals\n const effectiveActivationMode = portal.activationMode || 'click';\n if (effectiveActivationMode === 'click') {\n const shouldConfirm = portal.confirmNavigation !== false\n && (portal.title || portal.targetSceneName || portal.targetSceneId);\n if (shouldConfirm && portalPopupEl) {\n showPortalPopup(portal as PortalData);\n }\n else {\n handlePortalNavigation(portal);\n }\n }\n return; // Don't process hotspot clicks when portal is clicked\n }\n const hitResult = raycastHotspots(x, y);\n if (hitResult !== null) {\n const entity = hitResult.entity;\n const hotspot = hitResult.hotspot;\n console.log('[StorySplat Viewer] Hotspot clicked:', hotspot.title);\n // Handle hotspot audio play/pause (spatial or non-spatial)\n if (entity.audioElements && entity.audioElements.audio) {\n const audioElements = entity.audioElements as HotspotAudioElements;\n const audio = audioElements.audio;\n if (audio.paused) {\n // Resume AudioContext on user interaction (browser requirement)\n if (audioElements.audioCtx && audioElements.audioCtx.state === 'suspended') {\n audioElements.audioCtx.resume();\n }\n audio.play().catch(err => console.error('[Audio] Hotspot audio play failed:', err));\n console.log('[Audio] Hotspot audio started:', hotspot.title);\n }\n else {\n audio.pause();\n console.log('[Audio] Hotspot audio paused:', hotspot.title);\n }\n }\n // Handle video toggle (for click or autoplay mode - autoplay needs click to unmute)\n if (entity.videoElement && (entity.mediaTriggerMode === 'click' || entity.mediaTriggerMode === 'autoplay')) {\n const video = entity.videoElement as HTMLVideoElement;\n const alphaVideo = entity.alphaVideoElement as HTMLVideoElement | null;\n if (entity.mediaTriggerMode === 'autoplay' && !video.paused && video.muted) {\n // Autoplay mode: click unmutes if already playing\n video.muted = hotspot.videoMuted === false ? false : false; // Unmute on click\n console.log('[Hotspot] Video unmuted by click');\n }\n else if (video.paused) {\n // Play video\n playVideoHotspot(entity, hotspot);\n console.log('[Hotspot] Video started');\n }\n else {\n // Pause video\n pauseVideoHotspot(entity);\n console.log('[Hotspot] Video paused');\n }\n }\n // Show popup for click-activated hotspots with content\n // Default to 'click' if activationMode is not set\n const effectiveActivationMode = hotspot.activationMode || 'click';\n // Show popup if there's any displayable content (title, information, photo, iframe, or link)\n const hasPopupContent = hotspot.title || hotspot.information || hotspot.photoUrl || hotspot.iframeUrl || hotspot.externalLinkUrl;\n if (effectiveActivationMode === 'click' && hasPopupContent) {\n // In XR mode, show AR content plane instead of DOM popup\n if (isInXR) {\n if (arContentVisible) {\n // If already showing AR content, hide it (toggle behavior)\n hideARContent();\n }\n else {\n showARContent(hotspot as HotspotData);\n }\n }\n else {\n showHotspotPopup(container, hotspot as HotspotData, currentButtonLabels);\n }\n }\n // Handle hotspot teleportation (teleport to waypoint or percentage)\n const teleportWaypoint = hotspot.teleportWaypoint ?? hotspot.teleportToWaypoint;\n const teleportPercent = hotspot.teleportPercent ?? hotspot.teleportToPercent;\n const teleportMode = hotspot.teleportMode || 'animate'; // Default to animate for smooth experience\n let calculatedTargetProgress: number | null = null;\n if (teleportWaypoint !== undefined && teleportWaypoint !== -1) {\n // Teleport to specific waypoint\n const numWaypoints = config.waypoints?.length || 1;\n const targetIndex = Math.max(0, Math.min(teleportWaypoint, numWaypoints - 1));\n calculatedTargetProgress = numWaypoints > 1 ? targetIndex / (numWaypoints - 1) : 0;\n console.log('[Hotspot] Teleporting to waypoint:', targetIndex, '(progress:', calculatedTargetProgress, ', mode:', teleportMode, ')');\n }\n else if (teleportPercent !== undefined && teleportPercent !== -1) {\n // Teleport to specific percentage (0-100)\n calculatedTargetProgress = Math.max(0, Math.min(teleportPercent / 100, 1));\n console.log('[Hotspot] Teleporting to percent:', teleportPercent, '(progress:', calculatedTargetProgress, ', mode:', teleportMode, ')');\n }\n if (calculatedTargetProgress !== null) {\n if (teleportMode === 'instant') {\n // Instant jump - directly set position\n currentProgress = calculatedTargetProgress;\n targetProgress = calculatedTargetProgress; // Keep inertia system in sync\n updateCameraFromProgress(currentProgress);\n }\n else {\n // Animate (default) - smooth transition\n targetProgress = calculatedTargetProgress; // Keep inertia system in sync\n animateToProgress(calculatedTargetProgress, 800); // 800ms for smooth transition\n }\n }\n }\n });\n // Double-click/double-tap to focus camera on clicked point (explore mode only)\n async function handleDoubleClickFocus(screenX: number, screenY: number): Promise<void> {\n if (currentCameraMode !== 'explore')\n return;\n console.log('[StorySplat Viewer] Double-click focus at:', screenX, screenY);\n // First check if we hit a hotspot\n const hotspotHit = raycastHotspots(screenX, screenY);\n if (hotspotHit) {\n const pos = hotspotHit.entity.getPosition();\n console.log('[StorySplat Viewer] Focusing on hotspot at:', pos.x, pos.y, pos.z);\n cameraControls.focus(pos, false);\n return;\n }\n // Try to pick the GSplat using pc.Picker with depth buffer (PlayCanvas 2.14+)\n try {\n // Use 25% scale for performance (matching PlayCanvas official picking example)\n const pickerScale = 0.25;\n picker.resize(Math.floor(canvasEl.clientWidth * pickerScale), Math.floor(canvasEl.clientHeight * pickerScale));\n const worldLayer = app.scene.layers.getLayerByName('World');\n if (!worldLayer) {\n console.warn('[StorySplat Viewer] World layer not found');\n return;\n }\n picker.prepare(camera.camera!, app.scene, [worldLayer]);\n // Calculate scaled pick coordinates\n const scaledX = Math.floor(screenX * pickerScale);\n const scaledY = Math.floor(screenY * pickerScale);\n // Use getWorldPointAsync to get the actual 3D intersection point directly\n // This is the proper way to pick splats with depth-enabled picker\n const worldPoint = await picker.getWorldPointAsync(scaledX, scaledY);\n if (worldPoint) {\n // Validate the point is reasonable (not at infinity or behind camera)\n const cameraPos = camera.getPosition();\n const distance = cameraPos.distance(worldPoint);\n if (distance > 0.1 && distance < 1000) {\n console.log('[StorySplat Viewer] Focusing on GSplat at:', worldPoint.x.toFixed(2), worldPoint.y.toFixed(2), worldPoint.z.toFixed(2), 'distance:', distance.toFixed(2));\n cameraControls.focus(worldPoint, false);\n return;\n }\n }\n // Fallback: try mesh instance selection if world point failed\n const meshInstances = await picker.getSelectionAsync(scaledX, scaledY, 1, 1);\n if (meshInstances.length > 0) {\n const mi = meshInstances[0] as pc.MeshInstance;\n // Use AABB center as a rough focus point (only available on MeshInstance)\n if (!mi.aabb) {\n console.log('[StorySplat Viewer] No AABB available for picked object');\n return;\n }\n const aabbCenter = mi.aabb.center.clone();\n console.log('[StorySplat Viewer] Focusing on mesh AABB center:', aabbCenter.x.toFixed(2), aabbCenter.y.toFixed(2), aabbCenter.z.toFixed(2));\n cameraControls.focus(aabbCenter, false);\n }\n else {\n console.log('[StorySplat Viewer] No pick result at click point');\n }\n }\n catch (err) {\n console.warn('[StorySplat Viewer] Picking failed:', err);\n }\n }\n // Desktop double-click handler\n canvasEl.addEventListener('dblclick', (e: MouseEvent) => {\n const rect = canvasEl.getBoundingClientRect();\n handleDoubleClickFocus(e.clientX - rect.left, e.clientY - rect.top);\n });\n // Mobile double-tap detection\n let lastTapTime = 0;\n const DOUBLE_TAP_THRESHOLD = 300;\n canvasEl.addEventListener('touchend', (e: TouchEvent) => {\n if (e.changedTouches.length !== 1)\n return;\n const now = Date.now();\n if (now - lastTapTime < DOUBLE_TAP_THRESHOLD) {\n const touch = e.changedTouches[0];\n const rect = canvasEl.getBoundingClientRect();\n handleDoubleClickFocus(touch.clientX - rect.left, touch.clientY - rect.top);\n lastTapTime = 0;\n }\n else {\n lastTapTime = now;\n }\n });\n // Load the splat or frame sequence after app starts\n updateProgress(0.2, 'Initializing...');\n // Choose loader based on scene data\n const hasFrameSequence = scene.frameSequence && scene.frameSequence.frameUrls && scene.frameSequence.frameUrls.length > 0;\n const loadContent = hasFrameSequence ? loadFrameSequence : loadSplat;\n loadContent().then(() => {\n updateProgress(1.0, 'Ready!');\n // Create hotspots after splat is loaded\n createHotspots();\n // Create portals for scene-to-scene navigation\n createPortals();\n // Setup waypoint audio (audio interactions on waypoints)\n setupWaypointAudio();\n // Setup standalone audio emitters\n setupAudioEmitters();\n // Initialize particle systems\n console.log('[StorySplat Viewer] 🎆 Calling initParticleSystems...');\n initParticleSystems();\n // Initialize custom meshes (3D models with animations, audio)\n initCustomMeshes();\n // Initialize skybox\n initSkybox();\n // Initialize custom lights\n initCustomLights();\n // Start reveal effect IMMEDIATELY (before preloader hides)\n // The preloader is semi-transparent (0.85 opacity) so users can see the reveal through it\n if (revealScript) {\n revealScript.enabled = true;\n console.log('[StorySplat Viewer] Reveal effect started immediately');\n }\n // Hide preloader AFTER a short delay to let reveal effect start\n // This ensures users see the beginning of the reveal animation through the semi-transparent preloader\n setTimeout(() => {\n if (uiElements.preloader) {\n hidePreloader(uiElements.preloader);\n }\n }, 200);\n // Setup XR (VR/AR) if enabled\n setupXR();\n // Setup HTML Meshes if configured\n if (config.htmlMeshes && config.htmlMeshes.length > 0) {\n console.log('[StorySplat Viewer] Setting up HTML meshes:', config.htmlMeshes.length);\n const htmlMeshManager = setupHtmlMeshes(app, config.htmlMeshes);\n // Store manager reference for cleanup\n (app as ViewerRuntimeApp).__htmlMeshManager = htmlMeshManager;\n // Listen for progress updates to update HTML mesh visibility\n events.on('progressUpdate', () => {\n const scrollPercent = currentProgress * 100;\n const numWaypoints = config.waypoints?.length || 1;\n const waypointIndex = Math.round(currentProgress * Math.max(1, numWaypoints - 1));\n htmlMeshManager.updateVisibility(scrollPercent, waypointIndex);\n });\n }\n // Setup Custom Script System\n if (config.customScript && config.customScript.trim() !== '') {\n console.log('[StorySplat Viewer] Initializing custom script system...');\n const customScriptSystem = setupCustomScript(app, camera, canvas, config.customScript, () => currentProgress, () => currentWaypointIndex, () => hotspotEntities, () => splatEntity ? [splatEntity] : [], () => config.htmlMeshes || []);\n if (customScriptSystem) {\n // Store for cleanup\n (app as ViewerRuntimeApp).__customScriptSystem = customScriptSystem;\n console.log('[StorySplat Viewer] Custom script system initialized');\n }\n }\n // Check for URL parameters to support deep-linking\n // ?waypoint=2 will navigate to waypoint index 2\n // ?autoplay=true will start autoplay\n try {\n const urlParams = new URLSearchParams(window.location.search);\n const waypointParam = urlParams.get('waypoint');\n const autoplayParam = urlParams.get('autoplay');\n if (waypointParam !== null && config.waypoints && config.waypoints.length > 0) {\n const targetIndex = parseInt(waypointParam, 10);\n if (!isNaN(targetIndex) && targetIndex >= 0 && targetIndex < config.waypoints.length) {\n console.log('[StorySplat Viewer] URL param: navigating to waypoint', targetIndex);\n goToWaypoint(targetIndex);\n }\n }\n if (autoplayParam === 'true' && !options.autoPlay && !config.autoPlay) {\n console.log('[StorySplat Viewer] URL param: starting autoplay');\n play();\n }\n }\n catch (e) {\n // URLSearchParams may not be available in some environments\n console.warn('[StorySplat Viewer] Could not parse URL parameters:', e);\n }\n events.emit('ready');\n console.log('[StorySplat Viewer] Ready');\n // Apply the default camera mode now that the scene is loaded.\n // This ensures explore mode properly initializes CameraControls with\n // syncFromPose to the first waypoint position. Without this, loading\n // directly into explore mode would leave CameraControls uninitialized.\n setCameraMode(defaultMode);\n // Connect UI to viewer controls\n if (showUI) {\n connectUIToViewer(uiElements, {\n nextWaypoint,\n prevWaypoint,\n play,\n pause,\n isPlaying: () => isPlaying,\n getCurrentWaypointIndex: () => currentWaypointIndex,\n getWaypointCount: () => config.waypoints?.length || 0,\n getWaypoints: () => config.waypoints || [],\n setCameraMode,\n on: (event, callback) => events.on(event, callback)\n }, defaultMode, currentButtonLabels);\n // Wire up hotspot popup close button to stop audio/media\n const popupCloseBtn = container.querySelector('.storysplat-hotspot-popup-close');\n if (popupCloseBtn) {\n popupCloseBtn.addEventListener('click', () => {\n stopAllHotspotAudio();\n stopHotspotPopupMedia(container);\n });\n }\n // Setup waypoint list dropdown click handlers\n if (uiElements.waypointListContainer && config.waypoints && config.waypoints.length > 0) {\n setupWaypointListClickHandlers(uiElements, (index) => {\n console.log('[StorySplat Viewer] Waypoint list: jumping to waypoint', index);\n goToWaypoint(index);\n });\n // Update active waypoint in the list when waypoint changes\n events.on('waypointChange', ({ index }: {\n index: number;\n }) => {\n updateWaypointListActive(uiElements, index);\n });\n // Set initial active state\n updateWaypointListActive(uiElements, 0);\n }\n // Emit initial progress to update UI\n events.emit('progressUpdate', { progress: currentProgress, index: currentWaypointIndex });\n // Emit initial waypoint change to show first waypoint info\n if (config.waypoints && config.waypoints.length > 0) {\n events.emit('waypointChange', {\n index: 0,\n waypoint: config.waypoints[0],\n prevIndex: -1\n });\n }\n }\n // Start autoplay if enabled via options OR via scene config\n if (options.autoPlay || config.autoPlay) {\n play();\n }\n }).catch(err => {\n console.error('[StorySplat Viewer] Failed to initialize:', err);\n // Hide preloader even on error\n if (uiElements.preloader) {\n hidePreloader(uiElements.preloader);\n }\n events.emit('error', err);\n });\n // Handle resize\n const handleResize = () => {\n app.resizeCanvas();\n };\n window.addEventListener('resize', handleResize);\n // =====================================================\n // EDITOR MUTATION API (only exposed when editor: true)\n // =====================================================\n // Track editor hotspot/light entities by ID for mutation\n const editorHotspotMap = new Map<string, pc.Entity>();\n const editorLightMap = new Map<string, pc.Entity>();\n const editorParticleMap = new Map<string, pc.Entity>();\n const editorCustomMeshMap = new Map<string, pc.Entity>();\n const editorPortalMap = new Map<string, pc.Entity>();\n // Index existing hotspot entities by ID if in editor mode\n if (isEditorMode) {\n hotspotEntities.forEach((entity) => {\n const id = entity.hotspotData?.id;\n if (id)\n editorHotspotMap.set(id, entity);\n });\n lightEntities.forEach((entity, index: number) => {\n const lightConfig = config.lights?.[index];\n const id = lightConfig?.id || lightConfig?.name || `light-${index}`;\n editorLightMap.set(id, entity);\n });\n particleEntities.forEach((entity, id) => {\n editorParticleMap.set(id, entity);\n });\n portalEntities.forEach((entity) => {\n const id = entity.portalData?.id;\n if (id)\n editorPortalMap.set(id, entity);\n });\n }\n // Return viewer instance\n const instance: ViewerInstance = {\n app,\n canvas,\n // Navigation\n goToWaypoint: (index: number) => {\n if (currentCameraMode === 'explore') {\n throw new Error('[StorySplat Viewer] goToWaypoint() requires tour mode. Call setCameraMode(\"tour\") first.');\n }\n goToWaypoint(index);\n },\n nextWaypoint: () => {\n if (currentCameraMode === 'explore') {\n throw new Error('[StorySplat Viewer] nextWaypoint() requires tour mode. Call setCameraMode(\"tour\") first.');\n }\n nextWaypoint();\n },\n prevWaypoint: () => {\n if (currentCameraMode === 'explore') {\n throw new Error('[StorySplat Viewer] prevWaypoint() requires tour mode. Call setCameraMode(\"tour\") first.');\n }\n prevWaypoint();\n },\n getCurrentWaypointIndex: () => currentWaypointIndex,\n getWaypointCount: () => config.waypoints?.length || 0,\n // Camera\n setPosition: (x, y, z) => {\n if (currentCameraMode !== 'explore') {\n throw new Error('[StorySplat Viewer] setPosition() requires explore mode. Call setCameraMode(\"explore\") first.');\n }\n if (isPlaying) {\n throw new Error('[StorySplat Viewer] setPosition() cannot be used during autoplay. Call pause() or stop() first.');\n }\n // Disable controls to flush input, set camera, sync controller state, re-enable\n cameraControls.disable();\n camera.setPosition(x, y, z);\n cameraControls.syncFromCamera();\n cameraControls.enable();\n // Re-sync on next frame to ensure controller didn't interpolate back\n requestAnimationFrame(() => {\n camera.setPosition(x, y, z);\n cameraControls.syncFromCamera();\n });\n },\n setRotation: (x, y, z) => {\n if (currentCameraMode !== 'explore') {\n throw new Error('[StorySplat Viewer] setRotation() requires explore mode. Call setCameraMode(\"explore\") first.');\n }\n if (isPlaying) {\n throw new Error('[StorySplat Viewer] setRotation() cannot be used during autoplay. Call pause() or stop() first.');\n }\n cameraControls.disable();\n camera.setEulerAngles(x, y, z);\n cameraControls.syncFromCamera();\n cameraControls.enable();\n requestAnimationFrame(() => {\n camera.setEulerAngles(x, y, z);\n cameraControls.syncFromCamera();\n });\n },\n getPosition: () => {\n const pos = camera.getPosition();\n return { x: pos.x, y: pos.y, z: pos.z };\n },\n getRotation: () => {\n const rot = camera.getEulerAngles();\n return { x: rot.x, y: rot.y, z: rot.z };\n },\n // Playback\n play: () => {\n if (currentCameraMode === 'explore') {\n throw new Error('[StorySplat Viewer] play() requires tour mode. Call setCameraMode(\"tour\") first.');\n }\n play();\n },\n pause: () => {\n if (currentCameraMode === 'explore') {\n throw new Error('[StorySplat Viewer] pause() requires tour mode. Call setCameraMode(\"tour\") first.');\n }\n pause();\n },\n stop: () => {\n if (currentCameraMode === 'explore') {\n throw new Error('[StorySplat Viewer] stop() requires tour mode. Call setCameraMode(\"tour\") first.');\n }\n stop();\n },\n isPlaying: () => isPlaying,\n // 4DGS Frame Sequence API (only functional when frameSequence is configured)\n setFrame: (index: number) => frameSequencePlayer?.setFrame(index),\n getCurrentFrame: () => frameSequencePlayer?.getCurrentFrame() ?? 0,\n getTotalFrames: () => frameSequencePlayer?.getTotalFrames() ?? 0,\n setFps: (fps: number) => frameSequencePlayer?.setFps(fps),\n getFps: () => frameSequencePlayer?.getFps() ?? 24,\n getFrameProgress: () => frameSequencePlayer?.getProgress() ?? 0,\n setFrameProgress: (progress: number) => frameSequencePlayer?.setProgress(progress),\n // Splat API\n goToOriginalSplat,\n goToSplat: async (url: string) => {\n const primaryUrl = getPrimarySplatUrl();\n // Check if it's the original splat\n if (url === primaryUrl) {\n goToOriginalSplat();\n return;\n }\n // Check if it's in additionalSplats\n const splatConfig = additionalSplats.find(s => s.url === url);\n if (!splatConfig && !preloadedSplats.has(url)) {\n // Need to preload it first\n await preloadSplat(url);\n }\n // Hide current splat\n const currentUrl = currentSplatUrl || primaryUrl;\n if (currentUrl === primaryUrl && splatEntity) {\n hideSplat(splatEntity);\n }\n else if (currentUrl) {\n const currentEntity = preloadedSplats.get(currentUrl);\n if (currentEntity)\n currentEntity.enabled = false;\n }\n // Show the requested splat\n if (!showSplat(url)) {\n throw new Error(`[StorySplat Viewer] Failed to switch to splat: ${url}`);\n }\n // Apply explore mode if applicable\n if (splatConfig) {\n applyExploreModeForSplat(splatConfig.defaultExploreMode, false);\n }\n events.emit('splatChange', { url, isOriginal: false });\n },\n getCurrentSplatUrl,\n isShowingOriginalSplat,\n getAdditionalSplats: () => additionalSplats.map(s => ({\n url: s.url,\n name: s.name,\n waypointIndex: s.waypointIndex,\n percentage: s.percentage\n })),\n // Lifecycle\n destroy: () => {\n // Set destroyed flag first to prevent async operations from completing\n isDestroyed = true;\n pause();\n // Destroy frame sequence player if exists\n if (frameSequencePlayer) {\n frameSequencePlayer.destroy();\n frameSequencePlayer = null;\n }\n window.removeEventListener('resize', handleResize);\n // Remove UI elements\n if (uiElements.preloader)\n uiElements.preloader.remove();\n if (uiElements.scrollControls)\n uiElements.scrollControls.remove();\n if (uiElements.fullscreenButton)\n uiElements.fullscreenButton.remove();\n if (uiElements.helpButton)\n uiElements.helpButton.remove();\n if (uiElements.helpPanel)\n uiElements.helpPanel.remove();\n if (uiElements.waypointInfo)\n uiElements.waypointInfo.remove();\n if (uiElements.watermark)\n uiElements.watermark.remove();\n // Remove injected styles\n const styleEl = document.getElementById('storysplat-viewer-styles');\n if (styleEl)\n styleEl.remove();\n // Remove container class\n container.classList.remove('storysplat-viewer-container');\n // Cleanup animated GIFs\n animatedGifs.forEach(gif => gif.destroy());\n animatedGifs.length = 0;\n // Cleanup custom meshes\n cleanupCustomMeshes();\n // Clear collision references from CameraControls before destroying CharacterController\n // (CharacterController.destroy() destroys the collision entities)\n cameraControls.setCollisionEntities([]);\n // Cleanup CharacterController\n if (characterController) {\n characterController.destroy();\n }\n // Cleanup Custom Script System\n const destroyScriptSys = (app as ViewerRuntimeApp).__customScriptSystem;\n if (destroyScriptSys) {\n destroyScriptSys.dispose();\n }\n // Cleanup HTML Mesh Manager\n const destroyHtmlMgr = (app as ViewerRuntimeApp).__htmlMeshManager;\n if (destroyHtmlMgr) {\n destroyHtmlMgr.destroy();\n }\n // Cleanup AR content entity\n if (arContentEntity) {\n arContentEntity.destroy();\n arContentEntity = null;\n }\n if (arContentTexture) {\n arContentTexture.destroy();\n arContentTexture = null;\n }\n app.destroy();\n canvas.remove();\n },\n resize: handleResize,\n // Portal Navigation\n navigateToScene: async (sceneId: string) => {\n const portalData = { targetSceneId: sceneId, activationMode: 'click' };\n await handlePortalNavigation(portalData);\n },\n // Mode\n setCameraMode: (mode: 'tour' | 'explore') => setCameraMode(mode),\n getCameraMode: () => currentCameraMode,\n setExploreMode: (mode: 'orbit' | 'fly') => {\n if (currentCameraMode !== 'explore') {\n throw new Error('[StorySplat Viewer] setExploreMode() requires explore mode. Call setCameraMode(\"explore\") first.');\n }\n cameraControls.setMode(mode);\n updateExploreBtnState(mode);\n },\n // Progress (direct tour progress control)\n setProgress: (progress: number) => {\n if (currentCameraMode === 'explore') {\n throw new Error('[StorySplat Viewer] setProgress() requires tour mode. Call setCameraMode(\"tour\") first.');\n }\n setProgress(progress);\n },\n getProgress: () => currentProgress,\n // Audio\n muteAll: () => muteAllAudio(),\n unmuteAll: () => unmuteAllAudio(),\n isMuted: () => globalMuted,\n // Hotspot interaction\n getHotspots: () => {\n return hotspotEntities.map((entity) => {\n const h = entity.hotspotData;\n const pos = entity.getPosition();\n return {\n id: h?.id || '',\n title: h?.title || h?.information?.substring(0, 30) || '',\n type: h?.type || 'sphere',\n position: { x: pos.x, y: pos.y, z: pos.z }\n };\n });\n },\n triggerHotspot: (id: string) => {\n const entity = hotspotEntities.find((e) => (e as ViewerRuntimeEntity).hotspotData?.id === id);\n if (!entity) {\n throw new Error(`[StorySplat Viewer] Hotspot not found: ${id}`);\n }\n const hotspot = (entity as ViewerRuntimeEntity).hotspotData;\n if (hotspot) {\n showHotspotPopup(container, hotspot, currentButtonLabels);\n }\n },\n closeHotspot: () => {\n const popupEl = container.querySelector('.storysplat-hotspot-popup') as HTMLElement;\n const overlayEl = container.querySelector('.storysplat-hotspot-overlay') as HTMLElement;\n if (popupEl)\n popupEl.classList.remove('visible');\n if (overlayEl)\n overlayEl.classList.remove('visible');\n stopAllHotspotAudio();\n stopHotspotPopupMedia(container);\n },\n // UI customization\n setButtonLabels: (labels: Partial<ButtonLabels>) => {\n // Merge new labels with existing\n currentButtonLabels = { ...currentButtonLabels, ...labels };\n // Helper to get resolved label\n const gl = (key: keyof ButtonLabels) => getButtonLabel(currentButtonLabels, key);\n // Update DOM elements that display label text\n const modeBtn = (mode: string, key: keyof ButtonLabels) => {\n const el = container.querySelector(`.storysplat-mode-btn[data-mode=\"${mode}\"]`) as HTMLElement;\n if (el)\n el.textContent = gl(key);\n };\n modeBtn('tour', 'tour');\n modeBtn('explore', 'explore');\n modeBtn('walk', 'walk');\n // Explore sub-buttons (orbit/fly)\n const orbitBtn = container.querySelector('.storysplat-explore-btn[data-explore-mode=\"orbit\"]') as HTMLElement;\n if (orbitBtn)\n orbitBtn.textContent = gl('orbit');\n const flyBtn = container.querySelector('.storysplat-explore-btn[data-explore-mode=\"fly\"]') as HTMLElement;\n if (flyBtn)\n flyBtn.textContent = gl('fly');\n // Nav buttons\n const prevBtn = container.querySelector('.storysplat-btn-prev') as HTMLElement;\n if (prevBtn)\n prevBtn.textContent = gl('previous');\n const nextBtn = container.querySelector('.storysplat-btn-next') as HTMLElement;\n if (nextBtn)\n nextBtn.textContent = gl('next');\n // Waypoint list toggle\n const wpToggle = container.querySelector('.storysplat-waypoint-list-toggle') as HTMLElement;\n if (wpToggle) {\n const svg = wpToggle.querySelector('svg');\n wpToggle.textContent = '';\n wpToggle.append(gl('waypoints'));\n if (svg)\n wpToggle.appendChild(svg);\n wpToggle.setAttribute('aria-label', gl('waypoints'));\n }\n // XR buttons (only update if not in active XR session)\n const vrBtn = container.querySelector('.storysplat-vr-btn') as HTMLElement;\n if (vrBtn && !vrBtn.classList.contains('active')) {\n vrBtn.textContent = gl('vr');\n vrBtn.setAttribute('aria-label', gl('vr'));\n }\n const arBtn = container.querySelector('.storysplat-ar-btn') as HTMLElement;\n if (arBtn && !arBtn.classList.contains('active')) {\n arBtn.textContent = gl('ar');\n arBtn.setAttribute('aria-label', gl('ar'));\n }\n // Fullscreen button aria-label\n const fsBtn = container.querySelector('.storysplat-fullscreen-btn') as HTMLElement;\n if (fsBtn)\n fsBtn.setAttribute('aria-label', gl('fullscreen'));\n // Hotspot popup close button\n const closeBtn = container.querySelector('.storysplat-hotspot-popup-close') as HTMLElement;\n if (closeBtn)\n closeBtn.textContent = gl('close');\n // Portal popup buttons\n const confirmBtn = container.querySelector('.storysplat-portal-popup-confirm') as HTMLElement;\n if (confirmBtn)\n confirmBtn.textContent = gl('yes');\n const cancelBtn = container.querySelector('.storysplat-portal-popup-cancel') as HTMLElement;\n if (cancelBtn)\n cancelBtn.textContent = gl('cancel');\n // Help panel - rebuild using safe DOM methods\n const helpPanel = container.querySelector('.storysplat-help-panel') as HTMLElement;\n if (helpPanel) {\n helpPanel.textContent = '';\n const addEl = (tag: string, text: string, bold?: boolean) => {\n const el = document.createElement(tag);\n if (bold) {\n const b = document.createElement('strong');\n b.textContent = text;\n el.appendChild(b);\n }\n else\n el.textContent = text;\n helpPanel.appendChild(el);\n return el;\n };\n const addSpacer = () => helpPanel.appendChild(document.createElement('br'));\n addEl('h3', gl('helpTitle'));\n addEl('p', gl('helpCameraModes'), true);\n addEl('p', `\\u2022 ${gl('tour')} - ${gl('helpTourDesc')}`);\n addEl('p', `\\u2022 ${gl('explore')} - ${gl('helpExploreDesc')}`);\n addEl('p', `\\u2022 ${gl('walk')} - ${gl('helpWalkDesc')}`);\n addSpacer();\n addEl('p', `${gl('tour')} Mode:`, true);\n addEl('p', '\\u2022 Scroll - Move along path');\n addEl('p', '\\u2022 Drag - Look around');\n addSpacer();\n addEl('p', `${gl('explore')} Mode:`, true);\n addEl('p', '\\u2022 LMB Drag - Orbit camera');\n addEl('p', '\\u2022 RMB Drag - Fly/look');\n addEl('p', '\\u2022 WASD/QE - Move camera');\n addEl('p', '\\u2022 Shift - Move fast');\n addEl('p', '\\u2022 Scroll/Pinch - Zoom');\n addEl('p', '\\u2022 Double-click - Focus');\n addSpacer();\n addEl('p', `${gl('walk')} Mode:`, true);\n addEl('p', '\\u2022 Click to lock mouse');\n addEl('p', '\\u2022 WASD/Arrows - Move');\n addEl('p', '\\u2022 Mouse - Look around');\n addEl('p', '\\u2022 Shift - Sprint');\n addEl('p', '\\u2022 Space - Jump');\n }\n // Help button title\n const helpBtn = container.querySelector('.storysplat-help-btn') as HTMLElement;\n if (helpBtn)\n helpBtn.setAttribute('title', gl('helpTitle'));\n },\n // Events\n on: (event: ViewerEvent, callback) => events.on(event, callback),\n off: (event: ViewerEvent, callback) => events.off(event, callback)\n };\n // If editor mode, extend instance with mutation API\n if (isEditorMode) {\n const editorInstance = instance as EditorViewerInstance;\n // Camera (editor keeps its own references for backward compat)\n editorInstance.setCameraMode = (mode: 'tour' | 'explore') => setCameraMode(mode);\n editorInstance.getCameraMode = () => currentCameraMode;\n editorInstance.getCameraControls = () => cameraControls;\n // Scene access\n editorInstance.getApp = () => app;\n editorInstance.getSplatEntity = () => splatEntity;\n editorInstance.getAllHotspotEntities = () => editorHotspotMap;\n editorInstance.getAllLightEntities = () => editorLightMap;\n editorInstance.getCamera = () => camera;\n // Progress\n editorInstance.setProgress = (progress: number) => setProgress(progress);\n editorInstance.getProgress = () => currentProgress;\n // Hotspot mutation - uses the same full rendering as createHotspots()\n editorInstance.addHotspot = (hotspot: HotspotData): string => {\n const id = hotspot.id || `hotspot-${Date.now()}`;\n const index = hotspotEntities.length;\n const entity = createSingleHotspot(hotspot, index);\n editorHotspotMap.set(id, entity);\n return id;\n };\n editorInstance.removeHotspot = (id: string) => {\n const entity = editorHotspotMap.get(id);\n if (entity) {\n entity.destroy();\n editorHotspotMap.delete(id);\n const idx = hotspotEntities.indexOf(entity);\n if (idx >= 0)\n hotspotEntities.splice(idx, 1);\n }\n };\n editorInstance.updateHotspot = (id: string, data: Partial<HotspotData>) => {\n const entity = editorHotspotMap.get(id);\n if (!entity)\n return;\n if (data.position) {\n entity.setPosition(data.position.x, data.position.y, -(data.position.z));\n }\n const existing = (entity as ViewerRuntimeEntity).hotspotData || {};\n (entity as ViewerRuntimeEntity).hotspotData = { ...existing, ...data } as HotspotData;\n };\n // Light mutation\n // Light mutation - uses the same creation functions as initCustomLights()\n editorInstance.addLight = (lightCfg: LightConfig): string => {\n const id = lightCfg.id || lightCfg.name || `light-${Date.now()}`;\n let entity: pc.Entity | null = null;\n switch (lightCfg.type) {\n case 'point':\n entity = createPointLight(lightCfg);\n break;\n case 'directional':\n entity = createDirectionalLightEntity(lightCfg);\n break;\n case 'hemispheric':\n entity = createHemisphericLight(lightCfg);\n break;\n case 'ambient':\n createAmbientLight(lightCfg);\n break;\n case 'spot':\n entity = createSpotLight(lightCfg);\n break;\n default:\n entity = createPointLight(lightCfg);\n break;\n }\n if (entity) {\n app.root.addChild(entity);\n lightEntities.push(entity);\n editorLightMap.set(id, entity);\n }\n return id;\n };\n editorInstance.removeLight = (id: string) => {\n const entity = editorLightMap.get(id);\n if (entity) {\n entity.destroy();\n editorLightMap.delete(id);\n const idx = lightEntities.indexOf(entity);\n if (idx >= 0)\n lightEntities.splice(idx, 1);\n }\n };\n editorInstance.updateLight = (id: string, data: Partial<LightConfig>) => {\n const entity = editorLightMap.get(id);\n if (!entity)\n return;\n if (data.position) {\n entity.setPosition(data.position.x ?? 0, data.position.y ?? 0, -(data.position.z ?? 0));\n }\n if (entity.light) {\n if (data.intensity !== undefined)\n entity.light.intensity = data.intensity;\n if (data.range !== undefined)\n entity.light.range = data.range;\n if (data.castShadows !== undefined)\n entity.light.castShadows = data.castShadows;\n if (data.color) {\n entity.light.color = hexToColorForLights(data.color);\n }\n }\n };\n // Custom mesh mutation - uses loadCustomMesh() for full GLB/GLTF loading\n editorInstance.addCustomMesh = (meshCfg: CustomMeshConfig): string => {\n const id = meshCfg.id || meshCfg.name || `mesh-${Date.now()}`;\n const entity = loadCustomMesh(meshCfg, customMeshEntities.size);\n if (entity) {\n editorCustomMeshMap.set(id, entity);\n }\n return id;\n };\n editorInstance.removeCustomMesh = (id: string) => {\n const entity = editorCustomMeshMap.get(id);\n if (entity) {\n // Also remove from customMeshEntities if present\n customMeshEntities.forEach((meshData, key) => {\n if (meshData.entity === entity) {\n customMeshEntities.delete(key);\n }\n });\n entity.destroy();\n editorCustomMeshMap.delete(id);\n }\n };\n // Particle mutation - uses createParticleSystemEntity() + texture loading\n editorInstance.addParticleSystem = (ps: ParticleConfig): string => {\n const id = ps.id || ps.name || `particle-${Date.now()}`;\n // Create the entity with full particle system configuration\n const entity = createParticleSystemEntity(ps);\n // Load and apply texture (async)\n const textureName = ps.particleTexture || 'flare';\n let textureUrl: string;\n let textureCacheKey: string;\n if (textureName === 'custom' && ps.customTextureUrl) {\n textureUrl = ps.customTextureUrl;\n textureCacheKey = `custom_${id}`;\n }\n else {\n textureUrl = PARTICLE_TEXTURE_URLS[textureName] || PARTICLE_TEXTURE_URLS['flare'];\n textureCacheKey = textureName;\n }\n loadParticleTexture(textureCacheKey, textureUrl).then((texture) => {\n if (entity.particlesystem) {\n entity.particlesystem.colorMap = texture;\n }\n }).catch((err) => {\n console.warn('[Editor] Failed to load particle texture:', err);\n });\n app.root.addChild(entity);\n const safeName = id.replace(/[^a-zA-Z0-9]/g, '_');\n particleEntities.set(safeName, entity);\n editorParticleMap.set(id, entity);\n return id;\n };\n editorInstance.removeParticleSystem = (id: string) => {\n const safeName = id.replace(/[^a-zA-Z0-9]/g, '_');\n const entity = editorParticleMap.get(id) || particleEntities.get(safeName);\n if (entity) {\n entity.destroy();\n editorParticleMap.delete(id);\n particleEntities.delete(safeName);\n }\n };\n // Portal mutation - uses createSinglePortal() for full rendering\n editorInstance.addPortal = (portal: PortalData): string => {\n const id = portal.id || `portal-${Date.now()}`;\n const index = portalEntities.length;\n const entity = createSinglePortal(portal, index);\n editorPortalMap.set(id, entity);\n return id;\n };\n editorInstance.removePortal = (id: string) => {\n const entity = editorPortalMap.get(id);\n if (entity) {\n // Cleanup video element if present\n const videoEl = (entity as ViewerRuntimeEntity).videoElement;\n if (videoEl) {\n videoEl.pause();\n videoEl.src = '';\n }\n entity.destroy();\n editorPortalMap.delete(id);\n const idx = portalEntities.indexOf(entity);\n if (idx >= 0)\n portalEntities.splice(idx, 1);\n }\n };\n editorInstance.updatePortal = (id: string, data: Partial<PortalData>) => {\n const entity = editorPortalMap.get(id);\n if (!entity)\n return;\n if (data.position) {\n entity.setPosition(data.position.x ?? 0, data.position.y ?? 0, -(data.position.z ?? 0));\n }\n const existing = (entity as ViewerRuntimeEntity).portalData || {};\n (entity as ViewerRuntimeEntity).portalData = { ...existing, ...data } as PortalData;\n };\n // HTML Mesh mutation - uses HtmlMeshManager\n editorInstance.addHtmlMesh = (meshConfig: HtmlMeshConfig): string => {\n const id = meshConfig.id || `html-mesh-${Date.now()}`;\n let manager = (app as ViewerRuntimeApp).__htmlMeshManager;\n if (!manager) {\n manager = setupHtmlMeshes(app, []);\n (app as ViewerRuntimeApp).__htmlMeshManager = manager;\n }\n manager.createMesh({ ...meshConfig, id, html: meshConfig.html || meshConfig.htmlContent || '' });\n return id;\n };\n editorInstance.removeHtmlMesh = (id: string) => {\n const manager = (app as ViewerRuntimeApp).__htmlMeshManager;\n if (manager) {\n manager.destroyMesh(id);\n }\n };\n // Collision Mesh mutation\n editorInstance.addCollisionMesh = (meshConfig: CollisionMeshConfig): string => {\n const id = meshConfig.id || `collision-${Date.now()}`;\n const entity = new pc.Entity(`collision-mesh-${id}`);\n const pos = meshConfig.position;\n const px = Array.isArray(pos) ? pos[0] : pos.x;\n const py = Array.isArray(pos) ? pos[1] : pos.y;\n const pz = Array.isArray(pos) ? pos[2] : pos.z;\n entity.setPosition(px, py, -(pz ?? 0));\n if (meshConfig.rotation) {\n const rot = meshConfig.rotation;\n const rx = Array.isArray(rot) ? rot[0] : rot.x;\n const ry = Array.isArray(rot) ? rot[1] : rot.y;\n const rz = Array.isArray(rot) ? rot[2] : rot.z;\n entity.setEulerAngles(rx ?? 0, ry ?? 0, rz ?? 0);\n }\n if (meshConfig.scaling) {\n const s = meshConfig.scaling;\n const sx = Array.isArray(s) ? s[0] : s.x;\n const sy = Array.isArray(s) ? s[1] : s.y;\n const sz = Array.isArray(s) ? s[2] : s.z;\n entity.setLocalScale(sx ?? 1, sy ?? 1, sz ?? 1);\n }\n // Add render component for visualization\n const meshType = meshConfig.meshType || 'cube';\n if (meshType !== 'custom') {\n const renderType = meshType === 'cube' ? 'box' : meshType === 'floor' ? 'plane' : meshType;\n entity.addComponent('render', {\n type: renderType,\n castShadows: false,\n receiveShadows: false,\n });\n // Semi-transparent green material for collision mesh visualization\n const material = new pc.StandardMaterial();\n material.diffuse = new pc.Color(0, 1, 0);\n material.opacity = 0.3;\n material.blendType = pc.BLEND_NORMAL;\n material.depthWrite = false;\n material.update();\n if (entity.render) {\n entity.render.meshInstances.forEach((mi) => { mi.material = material; });\n }\n }\n entity.addComponent('collision', {\n type: meshType === 'sphere' ? 'sphere' : 'box',\n });\n entity.enabled = meshConfig.visible !== false;\n (entity as ViewerRuntimeEntity)._collisionMeshId = id;\n app.root.addChild(entity);\n return id;\n };\n editorInstance.removeCollisionMesh = (id: string) => {\n // Find and destroy the collision mesh entity by ID\n const children = app.root.children;\n for (let i = children.length - 1; i >= 0; i--) {\n if ((children[i] as ViewerRuntimeEntity)._collisionMeshId === id) {\n children[i].destroy();\n break;\n }\n }\n };\n // Audio Emitter mutation - uses same setup as setupAudioEmitters()\n editorInstance.addAudioEmitter = (emitterConfig: AudioEmitterConfig): string => {\n const id = emitterConfig.id || `emitter-${Date.now()}`;\n const position = emitterConfig.position || { x: 0, y: 0, z: 0 };\n const audioEntity = new pc.Entity(`audio-emitter-${id}`);\n audioEntity.setPosition(position.x, position.y, position.z);\n audioEntity.addComponent('sound', {\n positional: emitterConfig.spatialSound !== false,\n refDistance: emitterConfig.refDistance || 1,\n maxDistance: emitterConfig.maxDistance || 100,\n rollOffFactor: emitterConfig.rolloffFactor || 1,\n volume: emitterConfig.volume ?? 0.5,\n });\n audioEntity.sound?.addSlot(id, {\n volume: emitterConfig.volume ?? 0.5,\n loop: emitterConfig.loop !== false,\n autoPlay: false,\n overlap: false,\n });\n if (emitterConfig.url) {\n const audioAsset = new pc.Asset(`audio-emitter-${id}`, 'audio', { url: emitterConfig.url });\n audioAsset.on('load', () => {\n if (isDestroyed)\n return;\n const slot = audioEntity.sound?.slot(id);\n if (slot) {\n slot.asset = audioAsset.id;\n if (emitterConfig.autoplay !== false) {\n slot.play();\n }\n }\n });\n app.assets.add(audioAsset);\n app.assets.load(audioAsset);\n }\n app.root.addChild(audioEntity);\n audioEmitterMap.set(id, {\n entity: audioEntity,\n config: emitterConfig,\n slotId: id,\n playing: false,\n assetReady: false,\n });\n return id;\n };\n editorInstance.removeAudioEmitter = (id: string) => {\n const data = audioEmitterMap.get(id);\n if (data) {\n const slot = data.entity.sound?.slot(data.slotId);\n if (slot)\n slot.stop();\n data.entity.destroy();\n audioEmitterMap.delete(id);\n }\n };\n editorInstance.updateAudioEmitter = (id: string, data: Partial<AudioEmitterConfig>) => {\n const emitterData = audioEmitterMap.get(id);\n if (!emitterData)\n return;\n if (data.position) {\n emitterData.entity.setPosition(data.position.x, data.position.y, data.position.z);\n }\n if (data.volume !== undefined && emitterData.entity.sound) {\n emitterData.entity.sound.volume = data.volume;\n }\n emitterData.config = { ...emitterData.config, ...data };\n };\n // Environment\n editorInstance.setSkybox = (url: string | null, rotation?: number) => {\n setSkyboxRuntime(url, rotation);\n };\n editorInstance.setBackgroundColor = (color: {\n r: number;\n g: number;\n b: number;\n }) => {\n const pcColor = new pc.Color(color.r / 255, color.g / 255, color.b / 255);\n if (camera.camera) {\n camera.camera.clearColor = pcColor;\n }\n };\n editorInstance.setFOV = (newFov: number) => {\n if (camera.camera) {\n camera.camera.fov = newFov;\n }\n };\n // Waypoints (operate on config.waypoints directly)\n editorInstance.addWaypoint = (wp: WaypointData) => {\n if (!config.waypoints)\n config.waypoints = [];\n config.waypoints.push(wp);\n };\n editorInstance.removeWaypoint = (index: number) => {\n if (config.waypoints && index >= 0 && index < config.waypoints.length) {\n config.waypoints.splice(index, 1);\n }\n };\n editorInstance.updateWaypoint = (index: number, data: Partial<WaypointData>) => {\n if (config.waypoints && index >= 0 && index < config.waypoints.length) {\n Object.assign(config.waypoints[index], data);\n }\n };\n editorInstance.rebuildTourPath = () => {\n // The tour path is computed from config.waypoints on-the-fly in updateCameraFromProgress\n // No explicit rebuild needed - the next progress update will use the latest waypoints\n console.log('[Editor] Tour path will use updated waypoints on next progress update');\n };\n return editorInstance;\n }\n return instance;\n}\n/**\n * Create viewer from scene URL\n */\nexport async function createViewerFromUrl(container: HTMLElement, jsonUrl: string, options?: ViewerOptions): Promise<ViewerInstance> {\n console.log('[StorySplat Viewer] Fetching scene from:', jsonUrl);\n const response = await fetch(jsonUrl);\n if (!response.ok) {\n throw new Error(`Failed to fetch scene: ${response.statusText}`);\n }\n const scene: SceneData = await response.json();\n console.log('[StorySplat Viewer] Scene data loaded:', scene);\n return createViewer(container, scene, options);\n}\n// Utility functions\nfunction lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\nfunction lerpAngle(a: number, b: number, t: number): number {\n let diff = b - a;\n while (diff > 180)\n diff -= 360;\n while (diff < -180)\n diff += 360;\n return a + diff * t;\n}\nfunction easeInOutCubic(t: number): number {\n return t < 0.5\n ? 4 * t * t * t\n : 1 - Math.pow(-2 * t + 2, 3) / 2;\n}\n","/**\n * Create viewer from StorySplat Scene ID\n *\n * This module provides functionality to create a viewer by fetching\n * scene data from the StorySplat API using a scene ID.\n */\n\nimport { createViewer } from './createViewer';\nimport type { ViewerInstance, ViewerOptions, SceneData, ViewerLoadedData } from '../types';\n\n/**\n * Track embedded scene usage (views and bandwidth)\n * Sends tracking data to StorySplat API for analytics\n */\nasync function trackEmbedUsage(\n baseUrl: string,\n sceneId: string,\n ownerId: string,\n type: 'view' | 'bandwidth',\n bytes?: number\n): Promise<void> {\n try {\n const response = await fetch(`${baseUrl}/api/track-embed`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n sceneId,\n ownerId,\n type,\n ...(type === 'bandwidth' && bytes ? { bytes } : {})\n })\n });\n\n if (!response.ok) {\n console.warn(`[StorySplat] Failed to track ${type}:`, response.status);\n } else {\n console.log(`[StorySplat] Tracked ${type}${type === 'bandwidth' ? ` (${(bytes! / 1024 / 1024).toFixed(2)}MB)` : ''}`);\n }\n } catch (error) {\n // Silently fail - tracking shouldn't break the viewer\n console.warn(`[StorySplat] Error tracking ${type}:`, error);\n }\n}\n\n/**\n * Options for createViewerFromSceneId\n */\nexport interface ViewerFromSceneIdOptions extends ViewerOptions {\n /**\n * Base URL for the StorySplat API\n * @default 'https://discover.storysplat.com'\n */\n baseUrl?: string;\n\n /**\n * API key for accessing private scenes (future feature)\n */\n apiKey?: string;\n}\n\n/**\n * API response format from /api/scene/{sceneId}\n */\ninterface SceneApiResponse {\n success: boolean;\n data: SceneData;\n meta: {\n sceneId: string;\n ownerId: string; // User ID for tracking\n name: string;\n description?: string;\n userName?: string; // May be undefined for anonymous/deleted users\n userSlug?: string; // May be undefined for anonymous/deleted users\n thumbnailUrl: string;\n splatUrl: string;\n htmlUrl: string;\n playcanvasHtmlUrl?: string;\n views: number;\n createdAt: string | null;\n };\n}\n\n/**\n * Error thrown when scene fetching fails\n */\nexport class SceneNotFoundError extends Error {\n constructor(sceneId: string) {\n super(`Scene not found: ${sceneId}`);\n this.name = 'SceneNotFoundError';\n }\n}\n\n/**\n * Error thrown when API request fails\n */\nexport class SceneApiError extends Error {\n public statusCode: number;\n\n constructor(message: string, statusCode: number) {\n super(message);\n this.name = 'SceneApiError';\n this.statusCode = statusCode;\n }\n}\n\n/**\n * Get the default API base URL from environment or fallback to production\n */\nfunction getDefaultBaseUrl(): string {\n // Check for environment variable (works in Node.js, bundlers with define, etc.)\n if (typeof process !== 'undefined' && process.env?.STORYSPLAT_API_URL) {\n return process.env.STORYSPLAT_API_URL;\n }\n // Check for window-based config (for browser environments)\n const browserWindow = typeof window !== 'undefined'\n ? (window as Window & { __STORYSPLAT_API_URL__?: string })\n : undefined;\n if (browserWindow?.__STORYSPLAT_API_URL__) {\n return browserWindow.__STORYSPLAT_API_URL__;\n }\n // Default to production\n return 'https://discover.storysplat.com';\n}\n\n/**\n * Default API base URL\n */\nconst DEFAULT_BASE_URL = getDefaultBaseUrl();\n\n/**\n * Create a viewer from a StorySplat scene ID\n *\n * This function fetches scene data from the StorySplat API and creates\n * an embedded viewer. The scene must be public and owned by a user\n * with a public profile.\n *\n * @param container - HTML element to render the viewer in\n * @param sceneId - StorySplat scene ID from your dashboard\n * @param options - Viewer options including API configuration\n * @returns Promise resolving to ViewerInstance\n *\n * @example\n * ```typescript\n * import { createViewerFromSceneId } from 'storysplat-viewer';\n *\n * const viewer = await createViewerFromSceneId(\n * document.getElementById('viewer')!,\n * 'YOUR_SCENE_ID'\n * );\n *\n * // Control playback\n * viewer.play();\n * viewer.pause();\n *\n * // Navigate\n * viewer.nextWaypoint();\n * viewer.goToWaypoint(2);\n *\n * // Listen for events\n * viewer.on('ready', () => console.log('Viewer ready!'));\n * ```\n */\nexport async function createViewerFromSceneId(\n container: HTMLElement,\n sceneId: string,\n options: ViewerFromSceneIdOptions = {}\n): Promise<ViewerInstance> {\n const baseUrl = options.baseUrl || DEFAULT_BASE_URL;\n\n console.log(`[StorySplat Viewer] Fetching scene: ${sceneId}`);\n\n // Build request URL\n const apiUrl = `${baseUrl}/api/scene/${encodeURIComponent(sceneId)}`;\n\n // Build headers\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n };\n\n // Add API key if provided (for future private scene support)\n if (options.apiKey) {\n headers['Authorization'] = `Bearer ${options.apiKey}`;\n }\n\n // Fetch scene data\n const response = await fetch(apiUrl, {\n method: 'GET',\n headers,\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n throw new SceneNotFoundError(sceneId);\n }\n\n const errorText = await response.text();\n let errorMessage: string;\n\n try {\n const errorJson = JSON.parse(errorText);\n errorMessage = errorJson.error || `API error: ${response.status}`;\n } catch {\n errorMessage = `API error: ${response.status}`;\n }\n\n throw new SceneApiError(errorMessage, response.status);\n }\n\n const apiResponse: SceneApiResponse = await response.json();\n\n if (!apiResponse.success || !apiResponse.data) {\n throw new SceneApiError('Invalid API response format', 500);\n }\n\n console.log(`[StorySplat Viewer] Scene loaded: \"${apiResponse.meta.name}\"`);\n\n // Extract tracking info\n const ownerId = apiResponse.meta.ownerId;\n\n // Merge scene metadata into scene data if not present\n const sceneData: SceneData = {\n ...apiResponse.data,\n // Ensure these are set from metadata if not in scene data\n name: apiResponse.data.name || apiResponse.meta.name,\n thumbnailUrl: apiResponse.data.thumbnailUrl || apiResponse.meta.thumbnailUrl,\n };\n\n // Extract viewer-specific options (remove API-specific ones)\n const { baseUrl: _baseUrl, apiKey: _apiKey, ...viewerOptions } = options;\n\n // Create the viewer\n const viewer = createViewer(container, sceneData, viewerOptions);\n\n // Track view (fire and forget)\n trackEmbedUsage(baseUrl, sceneId, ownerId, 'view');\n\n // Track bandwidth when scene loads (only for StorySplat-hosted files)\n let bandwidthTracked = false;\n viewer.on('loaded', (data?: ViewerLoadedData) => {\n if (bandwidthTracked) return; // Only track once\n bandwidthTracked = true;\n\n if (data && data.bandwidthUsed > 0 && data.isStorySplatHosted) {\n trackEmbedUsage(baseUrl, sceneId, ownerId, 'bandwidth', data.bandwidthUsed);\n } else {\n console.log('[StorySplat] Bandwidth not tracked (self-hosted or no data)');\n }\n });\n\n return viewer;\n}\n\n/**\n * Fetch scene metadata without creating a viewer\n *\n * Useful for displaying scene information before loading the full viewer.\n *\n * @param sceneId - StorySplat scene ID\n * @param options - API configuration options\n * @returns Promise resolving to scene metadata\n *\n * @example\n * ```typescript\n * import { fetchSceneMeta } from 'storysplat-viewer';\n *\n * const meta = await fetchSceneMeta('YOUR_SCENE_ID');\n * console.log(`Scene: ${meta.name} by ${meta.userName}`);\n * console.log(`Views: ${meta.views}`);\n * ```\n */\nexport async function fetchSceneMeta(\n sceneId: string,\n options: { baseUrl?: string; apiKey?: string } = {}\n): Promise<{\n name: string;\n description: string;\n thumbnailUrl: string;\n userName: string; // Defaults to 'Unknown' if not available\n userSlug: string; // Defaults to 'unknown' if not available\n views: number;\n tags: string[];\n category?: string;\n createdAt: string | null;\n}> {\n const baseUrl = options.baseUrl || DEFAULT_BASE_URL;\n const apiUrl = `${baseUrl}/api/scene/${encodeURIComponent(sceneId)}/meta`;\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n };\n\n if (options.apiKey) {\n headers['Authorization'] = `Bearer ${options.apiKey}`;\n }\n\n const response = await fetch(apiUrl, {\n method: 'GET',\n headers,\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n throw new SceneNotFoundError(sceneId);\n }\n throw new SceneApiError(`API error: ${response.status}`, response.status);\n }\n\n const data = await response.json();\n\n // Ensure userName and userSlug have fallback values\n return {\n ...data,\n userName: data.userName || 'Unknown',\n userSlug: data.userSlug || 'unknown',\n };\n}\n","/**\n * GizmoManager - PlayCanvas Gizmo wrapper for editor\n *\n * Provides translate, rotate, and scale gizmos for 3D object manipulation.\n * API modeled after BabylonJS GizmoManager for easier migration.\n */\nimport * as pc from 'playcanvas';\ninterface GizmoNamespaceWithLayer {\n createLayer: (app: pc.Application) => pc.Layer;\n}\nexport type GizmoMode = 'translate' | 'rotate' | 'scale' | 'none';\nexport interface GizmoManagerConfig {\n snap?: boolean;\n snapIncrement?: number;\n coordSpace?: 'world' | 'local';\n}\nexport interface TransformEvent {\n entity: pc.Entity | null;\n position?: pc.Vec3;\n rotation?: pc.Quat;\n scale?: pc.Vec3;\n}\nexport class GizmoManager {\n private app: pc.Application;\n private camera: pc.CameraComponent;\n private gizmoLayer: pc.Layer;\n private translateGizmo: pc.TranslateGizmo | null = null;\n private rotateGizmo: pc.RotateGizmo | null = null;\n private scaleGizmo: pc.ScaleGizmo | null = null;\n private activeGizmo: pc.TranslateGizmo | pc.RotateGizmo | pc.ScaleGizmo | null = null;\n private attachedEntity: pc.Entity | null = null;\n private _mode: GizmoMode = 'none';\n // Event callbacks\n private onTransformStart?: (event: TransformEvent) => void;\n private onTransformMove?: (event: TransformEvent) => void;\n private onTransformEnd?: (event: TransformEvent) => void;\n constructor(app: pc.Application, camera: pc.CameraComponent, config: GizmoManagerConfig = {}) {\n this.app = app;\n this.camera = camera;\n // Create the gizmo layer\n this.gizmoLayer = (pc.Gizmo as unknown as GizmoNamespaceWithLayer).createLayer(app);\n // Create all gizmos\n this.translateGizmo = new pc.TranslateGizmo(camera, this.gizmoLayer);\n this.rotateGizmo = new pc.RotateGizmo(camera, this.gizmoLayer);\n this.scaleGizmo = new pc.ScaleGizmo(camera, this.gizmoLayer);\n // Apply config\n this.setSnap(config.snap ?? false, config.snapIncrement ?? 1);\n this.setCoordSpace(config.coordSpace ?? 'world');\n // Wire up events\n this.setupEvents(this.translateGizmo);\n this.setupEvents(this.rotateGizmo);\n this.setupEvents(this.scaleGizmo);\n }\n private setupEvents(gizmo: pc.TranslateGizmo | pc.RotateGizmo | pc.ScaleGizmo): void {\n gizmo.on('transform:start', () => {\n this.onTransformStart?.({\n entity: this.attachedEntity,\n position: this.attachedEntity?.getPosition().clone(),\n rotation: this.attachedEntity?.getRotation().clone(),\n scale: this.attachedEntity?.getLocalScale().clone()\n });\n });\n gizmo.on('transform:move', () => {\n this.onTransformMove?.({\n entity: this.attachedEntity,\n position: this.attachedEntity?.getPosition().clone(),\n rotation: this.attachedEntity?.getRotation().clone(),\n scale: this.attachedEntity?.getLocalScale().clone()\n });\n });\n gizmo.on('transform:end', () => {\n this.onTransformEnd?.({\n entity: this.attachedEntity,\n position: this.attachedEntity?.getPosition().clone(),\n rotation: this.attachedEntity?.getRotation().clone(),\n scale: this.attachedEntity?.getLocalScale().clone()\n });\n });\n }\n /**\n * Get current gizmo mode\n */\n get mode(): GizmoMode {\n return this._mode;\n }\n /**\n * Set the active gizmo mode\n */\n setMode(mode: GizmoMode): void {\n this._mode = mode;\n // Detach current gizmo\n this.activeGizmo?.detach();\n this.activeGizmo = null;\n // Activate the new gizmo\n switch (mode) {\n case 'translate':\n this.activeGizmo = this.translateGizmo;\n break;\n case 'rotate':\n this.activeGizmo = this.rotateGizmo;\n break;\n case 'scale':\n this.activeGizmo = this.scaleGizmo;\n break;\n case 'none':\n default:\n break;\n }\n // Re-attach to current entity if any\n if (this.activeGizmo && this.attachedEntity) {\n this.activeGizmo.attach([this.attachedEntity]);\n }\n }\n /**\n * Attach gizmo to an entity\n */\n attach(entity: pc.Entity | null): void {\n this.attachedEntity = entity;\n if (this.activeGizmo) {\n if (entity) {\n this.activeGizmo.attach([entity]);\n }\n else {\n this.activeGizmo.detach();\n }\n }\n }\n /**\n * Attach to mesh by finding the parent entity\n * (Compatibility with BabylonJS pattern)\n */\n attachToMesh(mesh: pc.Entity | null): void {\n this.attach(mesh);\n }\n /**\n * Detach gizmo from current entity\n */\n detach(): void {\n this.attachedEntity = null;\n this.activeGizmo?.detach();\n }\n /**\n * Enable/disable position gizmo\n * (BabylonJS compatibility)\n */\n set positionGizmoEnabled(enabled: boolean) {\n if (enabled && this._mode !== 'translate') {\n this.setMode('translate');\n }\n else if (!enabled && this._mode === 'translate') {\n this.setMode('none');\n }\n }\n get positionGizmoEnabled(): boolean {\n return this._mode === 'translate';\n }\n /**\n * Enable/disable rotation gizmo\n * (BabylonJS compatibility)\n */\n set rotationGizmoEnabled(enabled: boolean) {\n if (enabled && this._mode !== 'rotate') {\n this.setMode('rotate');\n }\n else if (!enabled && this._mode === 'rotate') {\n this.setMode('none');\n }\n }\n get rotationGizmoEnabled(): boolean {\n return this._mode === 'rotate';\n }\n /**\n * Enable/disable scale gizmo\n * (BabylonJS compatibility)\n */\n set scaleGizmoEnabled(enabled: boolean) {\n if (enabled && this._mode !== 'scale') {\n this.setMode('scale');\n }\n else if (!enabled && this._mode === 'scale') {\n this.setMode('none');\n }\n }\n get scaleGizmoEnabled(): boolean {\n return this._mode === 'scale';\n }\n /**\n * Set snap enabled and increment\n */\n setSnap(enabled: boolean, increment?: number): void {\n if (this.translateGizmo) {\n this.translateGizmo.snap = enabled;\n if (increment !== undefined) {\n this.translateGizmo.snapIncrement = increment;\n }\n }\n if (this.rotateGizmo) {\n this.rotateGizmo.snap = enabled;\n if (increment !== undefined) {\n this.rotateGizmo.snapIncrement = increment;\n }\n }\n if (this.scaleGizmo) {\n this.scaleGizmo.snap = enabled;\n if (increment !== undefined) {\n this.scaleGizmo.snapIncrement = increment;\n }\n }\n }\n /**\n * Set coordinate space (world or local)\n */\n setCoordSpace(space: 'world' | 'local'): void {\n if (this.translateGizmo) {\n this.translateGizmo.coordSpace = space;\n }\n if (this.rotateGizmo) {\n this.rotateGizmo.coordSpace = space;\n }\n if (this.scaleGizmo) {\n this.scaleGizmo.coordSpace = space;\n }\n }\n /**\n * Set transform event callbacks\n */\n onTransform(callbacks: {\n start?: (event: TransformEvent) => void;\n move?: (event: TransformEvent) => void;\n end?: (event: TransformEvent) => void;\n }): void {\n this.onTransformStart = callbacks.start;\n this.onTransformMove = callbacks.move;\n this.onTransformEnd = callbacks.end;\n }\n /**\n * Update gizmo (call each frame if needed)\n */\n update(): void {\n // PlayCanvas gizmos auto-update, but we can force refresh if needed\n this.activeGizmo?.update();\n }\n /**\n * Destroy the gizmo manager and clean up resources\n */\n destroy(): void {\n this.translateGizmo?.destroy();\n this.rotateGizmo?.destroy();\n this.scaleGizmo?.destroy();\n this.translateGizmo = null;\n this.rotateGizmo = null;\n this.scaleGizmo = null;\n this.activeGizmo = null;\n this.attachedEntity = null;\n }\n}\n","/**\n * SelectionManager - PlayCanvas entity selection for editor\n *\n * Handles raycasting, selection state, and visual feedback.\n */\n\nimport * as pc from 'playcanvas';\n\nexport interface SelectionEvent {\n entity: pc.Entity | null;\n previousEntity: pc.Entity | null;\n}\n\nexport interface SelectionManagerConfig {\n highlightColor?: pc.Color;\n multiSelect?: boolean;\n}\n\nexport class SelectionManager {\n private app: pc.Application;\n private camera: pc.Entity;\n private picker: pc.Picker;\n\n private selectedEntities: Set<pc.Entity> = new Set();\n private highlightedEntity: pc.Entity | null = null;\n private config: SelectionManagerConfig;\n\n // Original materials storage for highlight restoration\n private originalMaterials: Map<pc.Entity, Map<number, pc.Material>> = new Map();\n\n // Event callbacks\n private onSelectionChange?: (event: SelectionEvent) => void;\n\n constructor(\n app: pc.Application,\n camera: pc.Entity,\n config: SelectionManagerConfig = {}\n ) {\n this.app = app;\n this.camera = camera;\n this.config = {\n highlightColor: config.highlightColor ?? new pc.Color(0.2, 0.5, 1, 1),\n multiSelect: config.multiSelect ?? false\n };\n\n // Create picker with depth enabled for GSplat support (PlayCanvas 2.14+)\n this.picker = new pc.Picker(app, 1, 1, true);\n }\n\n /**\n * Pick entity at screen coordinates\n */\n async pickAtScreenPosition(x: number, y: number): Promise<pc.Entity | null> {\n const canvas = this.app.graphicsDevice.canvas;\n const cameraComponent = this.camera.camera;\n\n if (!cameraComponent || !canvas) {\n return null;\n }\n\n // Resize picker to a small region around the click point\n const pickerScale = 0.25;\n const pickerWidth = Math.max(1, Math.floor(canvas.clientWidth * pickerScale));\n const pickerHeight = Math.max(1, Math.floor(canvas.clientHeight * pickerScale));\n\n this.picker.resize(pickerWidth, pickerHeight);\n\n // Get the world layer\n const worldLayer = this.app.scene.layers.getLayerByName('World');\n if (!worldLayer) {\n return null;\n }\n\n // Prepare the picker\n this.picker.prepare(cameraComponent, this.app.scene, [worldLayer]);\n\n // Scale coordinates to picker size\n const pickerX = Math.floor(x * pickerScale);\n const pickerY = Math.floor(y * pickerScale);\n\n // Get selection at point\n const selection = this.picker.getSelection(pickerX, pickerY);\n\n if (selection && selection.length > 0) {\n // Return the first mesh instance's entity\n const meshInstance = selection[0] as pc.MeshInstance;\n if (meshInstance && meshInstance.node) {\n // Find the top-level entity (might be nested)\n let entity = meshInstance.node as pc.Entity;\n while (entity.parent && entity.parent !== this.app.root) {\n const parent = entity.parent as pc.Entity;\n // Stop if parent has a specific tag or is a root-level scene entity\n if (parent.tags?.has('selectable') || parent.tags?.has('hotspot') ||\n parent.tags?.has('waypoint') || parent.tags?.has('light')) {\n entity = parent;\n break;\n }\n entity = parent;\n }\n return entity;\n }\n }\n\n return null;\n }\n\n /**\n * Get world point at screen coordinates (for double-click focus)\n */\n async getWorldPointAtScreen(x: number, y: number): Promise<pc.Vec3 | null> {\n const canvas = this.app.graphicsDevice.canvas;\n const cameraComponent = this.camera.camera;\n\n if (!cameraComponent || !canvas) {\n return null;\n }\n\n const pickerScale = 0.25;\n const pickerWidth = Math.max(1, Math.floor(canvas.clientWidth * pickerScale));\n const pickerHeight = Math.max(1, Math.floor(canvas.clientHeight * pickerScale));\n\n this.picker.resize(pickerWidth, pickerHeight);\n\n const worldLayer = this.app.scene.layers.getLayerByName('World');\n if (!worldLayer) {\n return null;\n }\n\n this.picker.prepare(cameraComponent, this.app.scene, [worldLayer]);\n\n const pickerX = Math.floor(x * pickerScale);\n const pickerY = Math.floor(y * pickerScale);\n\n try {\n // Use async world point method for GSplat support\n const worldPoint = await this.picker.getWorldPointAsync(pickerX, pickerY);\n return worldPoint || null;\n } catch (err) {\n return null;\n }\n }\n\n /**\n * Select an entity\n */\n select(entity: pc.Entity | null, additive: boolean = false): void {\n const previousEntity = this.getSelectedEntity();\n\n if (!additive) {\n // Clear previous selection\n this.deselectAll();\n }\n\n if (entity) {\n this.selectedEntities.add(entity);\n this.applyHighlight(entity);\n }\n\n this.onSelectionChange?.({\n entity,\n previousEntity\n });\n }\n\n /**\n * Deselect a specific entity\n */\n deselect(entity: pc.Entity): void {\n if (this.selectedEntities.has(entity)) {\n this.selectedEntities.delete(entity);\n this.removeHighlight(entity);\n\n this.onSelectionChange?.({\n entity: this.getSelectedEntity(),\n previousEntity: entity\n });\n }\n }\n\n /**\n * Deselect all entities\n */\n deselectAll(): void {\n const previousEntity = this.getSelectedEntity();\n\n this.selectedEntities.forEach(entity => {\n this.removeHighlight(entity);\n });\n this.selectedEntities.clear();\n\n if (previousEntity) {\n this.onSelectionChange?.({\n entity: null,\n previousEntity\n });\n }\n }\n\n /**\n * Check if an entity is selected\n */\n isSelected(entity: pc.Entity): boolean {\n return this.selectedEntities.has(entity);\n }\n\n /**\n * Get the first selected entity (for single selection mode)\n */\n getSelectedEntity(): pc.Entity | null {\n const entities = Array.from(this.selectedEntities);\n return entities.length > 0 ? entities[0] : null;\n }\n\n /**\n * Get all selected entities\n */\n getSelectedEntities(): pc.Entity[] {\n return Array.from(this.selectedEntities);\n }\n\n /**\n * Apply visual highlight to entity\n */\n private applyHighlight(entity: pc.Entity): void {\n // Store original materials and apply highlight\n if (!entity.render) return;\n\n const materialMap = new Map<number, pc.Material>();\n entity.render.meshInstances.forEach((mi, index) => {\n if (mi.material) {\n materialMap.set(index, mi.material);\n\n // Create highlighted material clone\n const highlightMaterial = mi.material.clone();\n if (highlightMaterial instanceof pc.StandardMaterial) {\n highlightMaterial.emissive = this.config.highlightColor!;\n highlightMaterial.emissiveIntensity = 0.3;\n highlightMaterial.update();\n }\n mi.material = highlightMaterial;\n }\n });\n\n this.originalMaterials.set(entity, materialMap);\n }\n\n /**\n * Remove visual highlight from entity\n */\n private removeHighlight(entity: pc.Entity): void {\n const materialMap = this.originalMaterials.get(entity);\n if (!materialMap || !entity.render) return;\n\n entity.render.meshInstances.forEach((mi, index) => {\n const originalMaterial = materialMap.get(index);\n if (originalMaterial) {\n mi.material = originalMaterial;\n }\n });\n\n this.originalMaterials.delete(entity);\n }\n\n /**\n * Set selection change callback\n */\n onSelect(callback: (event: SelectionEvent) => void): void {\n this.onSelectionChange = callback;\n }\n\n /**\n * Handle pointer down event for selection\n */\n async handlePointerDown(event: MouseEvent | TouchEvent, additive: boolean = false): Promise<pc.Entity | null> {\n let x: number, y: number;\n\n if (event instanceof MouseEvent) {\n x = event.clientX;\n y = event.clientY;\n } else {\n const touch = event.touches[0];\n x = touch.clientX;\n y = touch.clientY;\n }\n\n const entity = await this.pickAtScreenPosition(x, y);\n\n if (entity) {\n this.select(entity, additive);\n } else if (!additive) {\n this.deselectAll();\n }\n\n return entity;\n }\n\n /**\n * Destroy the selection manager\n */\n destroy(): void {\n this.deselectAll();\n this.selectedEntities.clear();\n this.originalMaterials.clear();\n }\n}\n","/**\n * EditorCameraController - Edit-mode orbit camera for the StorySplat editor\n *\n * Provides an independent orbit camera for scene editing, separate from the\n * tour/explore camera. Also renders an FPV camera visualization mesh showing\n * where the tour camera is positioned.\n *\n * Only instantiated by the editor, never by the viewer.\n */\n\nimport * as pc from 'playcanvas';\nimport { CameraControls } from '../dynamic-viewer/CameraControls';\n\nexport interface EditorCameraControllerConfig {\n /** Initial orbit distance from target */\n orbitDistance?: number;\n /** Movement speed */\n moveSpeed?: number;\n /** Rotation sensitivity */\n rotateSensitivity?: number;\n /** Whether to show FPV camera visualization */\n showFPVVisualization?: boolean;\n}\n\nexport class EditorCameraController {\n private app: pc.Application;\n private cameraControls: CameraControls;\n private editCamera: pc.Entity;\n private tourCamera: pc.Entity;\n private fpvEntity: pc.Entity | null = null;\n private _enabled = false;\n private config: EditorCameraControllerConfig;\n\n constructor(\n app: pc.Application,\n tourCamera: pc.Entity,\n cameraControls: CameraControls,\n config: EditorCameraControllerConfig = {}\n ) {\n this.app = app;\n this.tourCamera = tourCamera;\n this.cameraControls = cameraControls;\n this.config = config;\n\n // Create a separate edit camera entity\n this.editCamera = new pc.Entity('editCamera');\n const tourCam = tourCamera.camera;\n this.editCamera.addComponent('camera', {\n clearColor: tourCam?.clearColor || new pc.Color(0.1, 0.1, 0.1),\n fov: tourCam?.fov || 60,\n nearClip: tourCam?.nearClip || 0.1,\n farClip: tourCam?.farClip || 1000\n });\n\n // Position edit camera near tour camera initially\n const pos = tourCamera.getPosition();\n this.editCamera.setPosition(pos.x, pos.y + 5, pos.z + 10);\n this.editCamera.lookAt(pos);\n this.editCamera.enabled = false;\n\n app.root.addChild(this.editCamera);\n\n // Create FPV visualization (small cone/pyramid showing tour camera)\n if (config.showFPVVisualization !== false) {\n this.createFPVVisualization();\n }\n }\n\n private createFPVVisualization(): void {\n this.fpvEntity = new pc.Entity('fpv-camera-viz');\n\n // Use a cone to represent the camera frustum\n this.fpvEntity.addComponent('render', {\n type: 'cone',\n castShadows: false,\n receiveShadows: false\n });\n\n this.fpvEntity.setLocalScale(0.3, 0.5, 0.3);\n this.fpvEntity.enabled = false;\n this.app.root.addChild(this.fpvEntity);\n\n // Update FPV position each frame\n this.app.on('update', () => {\n if (this.fpvEntity && this._enabled) {\n const tourPos = this.tourCamera.getPosition();\n const tourRot = this.tourCamera.getRotation();\n this.fpvEntity.setPosition(tourPos);\n this.fpvEntity.setRotation(tourRot);\n // Rotate to point cone forward (cone points up by default)\n this.fpvEntity.rotateLocal(90, 0, 0);\n }\n });\n }\n\n /** Enable edit camera mode (switches from tour camera to edit camera) */\n enable(): void {\n this._enabled = true;\n this.tourCamera.enabled = false;\n this.editCamera.enabled = true;\n this.cameraControls.disable();\n\n // Position edit camera from current tour camera\n const pos = this.tourCamera.getPosition();\n this.editCamera.setPosition(pos.x, pos.y + 5, pos.z + 10);\n this.editCamera.lookAt(pos);\n\n if (this.fpvEntity) {\n this.fpvEntity.enabled = true;\n }\n }\n\n /** Disable edit camera mode (switches back to tour camera) */\n disable(): void {\n this._enabled = false;\n this.editCamera.enabled = false;\n this.tourCamera.enabled = true;\n\n if (this.fpvEntity) {\n this.fpvEntity.enabled = false;\n }\n }\n\n get enabled(): boolean {\n return this._enabled;\n }\n\n /** Get the edit camera entity */\n getCamera(): pc.Entity {\n return this.editCamera;\n }\n\n /** Focus edit camera on a target point */\n focusOn(target: pc.Vec3): void {\n const dist = this.config.orbitDistance || 10;\n this.editCamera.setPosition(target.x, target.y + dist * 0.5, target.z + dist);\n this.editCamera.lookAt(target);\n }\n\n /** Sync edit camera clear color with tour camera */\n syncClearColor(): void {\n if (this.editCamera.camera && this.tourCamera.camera) {\n this.editCamera.camera.clearColor = this.tourCamera.camera.clearColor;\n }\n }\n\n /** Update FOV on both cameras */\n setFOV(fov: number): void {\n if (this.editCamera.camera) this.editCamera.camera.fov = fov;\n }\n\n destroy(): void {\n this.disable();\n this.editCamera.destroy();\n if (this.fpvEntity) {\n this.fpvEntity.destroy();\n this.fpvEntity = null;\n }\n }\n}\n"],"names":["escapeHtml","str","replace","escapeForInlineScript","generateHTML","sceneData","options","cdnUrl","title","name","description","faviconUrl","customCSS","lazyLoad","optionsLazyLoad","lazyLoadButtonText","optionsLazyLoadButtonText","uiOptions","faviconTag","customCSSBlock","sceneDataJSON","JSON","stringify","key","value","HTMLElement","async","generateHTMLFromUrl","jsonUrl","response","fetch","ok","Error","statusText","json","transformSceneToExportProps","scene","originalUrl","loadedModelUrl","splatUrl","sogUrl","sogModelUrl","compressedPlyUrl","lodMetaUrl","legacySkyboxUrl","activeSkyboxUrl","skyboxUrl","undefined","resolvedSkybox","skybox","url","rotation","skyboxRotation","originalExt","split","pop","toLowerCase","isPlayCanvasCompatible","includes","console","warn","log","original","selected","waypoints","map","wp","fov","Math","PI","info","interactions","infoInteraction","find","i","type","text","data","maybe","getInteractionText","position","x","y","z","duration","triggerDistance","resolvedParticles","Array","isArray","particleSystems","length","particles","sceneId","userId","userName","thumbnailUrl","fallbackUrls","filter","scale","splatScale","splatPosition","cameraPosition","splatRotation","cameraRotation","invertXScale","invertYScale","hotspots","portals","customMeshes","htmlMeshes","lights","collisionMeshesData","playerHeight","uiColor","showStartExperience","showWatermark","hideFullscreenButton","hideInfoButton","hideMuteButton","hideHelpButton","hideNavigator","showWaypointList","hideWatermark","watermarkText","watermarkLink","buttonPosition","buttonLabels","customPreloaderLogoUrl","lazyLoadThumbnailUrl","lazyLoadThumbnailType","uiType","debugMode","defaultCameraMode","allowedCameraModes","cameraMovementSpeed","cameraRotationSensitivity","cameraDamping","invertCameraRotation","includeScrollControls","scrollButtonMode","scrollAmount","scrollSpeed","transitionSpeed","autoPlayEnabled","autoplaySpeed","loopMode","includeXR","xrMode","customScript","templateType","additionalSplats","keepMeshesInMemory","initialSplatExploreMode","audioEmitters","frameSequence","exportPropsToViewerConfig","props","cameraMode","autoPlay","nearClip","minClipPlane","farClip","maxClipPlane","DEFAULT_BUTTON_LABELS","tour","explore","hybrid","walk","orbit","fly","next","previous","startExperience","fullscreen","mute","unmute","close","yes","cancel","switchScenes","hotspotDefaultTitle","openExternalLink","vr","ar","exitVr","exitAr","loading","loadingScene","helpTitle","helpCameraModes","helpTourDesc","helpExploreDesc","helpWalkDesc","errorWebGLTitle","errorWebGLMessage","percentageFormat","getButtonLabel","labels","injectStyles","template","existingStyle","document","getElementById","remove","style","createElement","id","textContent","generateTemplateOverrides","generateViewerStyles","head","appendChild","createPreloader","container","customLogoUrl","preloader","className","useCustomLogo","innerHTML","Promise","resolve","customElements","get","existingScript","querySelector","addEventListener","script","src","onload","hidePreloader","classList","add","setTimeout","connectUIToViewer","elements","viewer","prevBtn","scrollControls","nextBtn","playBtn","prevWaypoint","nextWaypoint","updatePlayButtonIcon","isPlaying","pause","play","on","helpButton","helpPanel","toggle","fullscreenButton","test","navigator","userAgent","platform","maxTouchPoints","display","parentElement","doc","fullscreenElement","webkitFullscreenElement","exitFullscreen","webkitExitFullscreen","expandIcon","compressIcon","requestFullscreen","webkitRequestFullscreen","handleFullscreenChange","isFullscreen","setTourControlsVisible","visible","progressText","progressContainer","scrollButtons","setExploreControlsVisible","exploreControls","setCameraMode","modeButtonContainer","modeButtons","querySelectorAll","forEach","btn","mode","getAttribute","b","btnMode","lastProgressUpdate","lastDisplayedPercentage","progress","percentage","max","min","roundedPercentage","round","now","performance","progressBar","width","String","lastDisplayedWaypointIndex","index","waypoint","waypointInfo","titleEl","descEl","getWaypoints","showHotspotPopup","hotspot","popup","contentEl","closeBtn","cssText","activationMode","hasMediaContent","photoUrl","popupVideoUrl","contentType","iframeUrl","bgAlpha","backgroundAlpha","backgroundColor","hex","r","parseInt","substring","g","textColor","color","fontFamily","fontSize","closeButtonColor","contentHtml","information","externalLinkUrl","btnColor","externalLinkButtonColor","externalLinkText","setJoystickVisible","joystick","joystickThumb","transform","lookZone","updateJoystickPosition","active","dx","dy","maxRadius","distance","sqrt","clampedDistance","clampedX","clampedY","updateLookZoneState","updateWaypointListActive","waypointListContainer","item","tmpV1","pc","Vec3","tmpV2","pose","Pose","frame","InputFrame","move","rotate","applyDeadZone","stick","low","high","mag","fill","screenToWorld","camera","dz","out","aspectRatio","horizontalFov","projection","orthoHeight","app","system","height","graphicsDevice","clientRect","set","halfSize","PROJECTION_PERSPECTIVE","halfSlice","tan","math","DEG_TO_RAD","mul","CameraControls","constructor","config","this","enabled","_mode","_enableOrbit","_enableFly","enablePan","_pose","_preFocusMode","_startZoomDist","_pitchRange","Vec2","_yawRange","Infinity","_lastFocusPoint","_zoomRange","_state","axis","shift","ctrl","mouse","touches","moveSpeed","moveFastSpeed","moveSlowSpeed","rotateSpeed","rotateTouchSens","rotateJoystickSens","zoomSpeed","zoomPinchSens","keyboardSpeedMultiplier","gamepadDeadZone","invertRotation","joystickEventName","_collisionEntities","_collisionRadius","_prevPosition","_prevPositionValid","_collisionTestVec","_destroyHandler","cameraComponent","_flyController","FlyController","_orbitController","OrbitController","_focusController","FocusController","moveDamping","rotateDamping","zoomDamping","zoomRange","canvas","_desktopInput","KeyboardMouseSource","_orbitMobileInput","MultiTouchSource","_flyMobileInput","DualGestureSource","_gamepadInput","GamepadSource","attach","bx","by","sx","sy","fire","look","getPosition","ZERO","_setMode","_controller","enableOrbit","enableFly","focusPoint","focusDamping","pitchRange","yawRange","mobileInputLayout","enable","damping","point","getFocus","range","copy","clamp","layout","previousMode","detach","_lastYaw","angles","setMode","focus","resetZoom","zoomDist","forward","mulScalar","sub","normalize","reset","syncFromCamera","target","clone","focusDistance","yaw","eulerAngles","getEulerAngles","syncFromPose","focusTarget","setPosition","setRotation","tempEntity","Entity","disable","read","update","dt","keyCode","button","wheel","touch","pinch","count","leftInput","rightInput","leftStick","rightStick","D","A","RIGHT","LEFT","E","Q","W","S","UP","DOWN","SHIFT","CTRL","double","desktopPan","mobileJoystick","endsWith","moveMult","zoomMult","zoomTouchMult","rotateMult","rotateTouchMult","rotateJoystickMult","deltas","v","keyMove","panMove","wheelMove","append","mouseRotate","flyMove","orbitMove","pinchMove","orbitRotate","flyRotate","stickMove","stickRotate","xr","focusInterrupt","focusComplete","complete","newPos","prev","corrected","checkCollision","yawDelta","setEulerAngles","setCollisionEntities","entities","radius","entity","entityPos","entityScale","getLocalScale","meshType","_collisionMeshType","halfWidth","halfHeight","halfDepth","centerX","centerY","centerZ","customBounds","_collisionBounds","center","halfExtents","abs","destroy","toXYZ","fallback","CharacterController","velocity","isGrounded","pitch","keys","mouseLocked","sprintMultiplier","lookSensitivity","gravity","maxFallSpeed","jumpVelocity","collisionRadius","stepHeight","groundCheckDistance","horizontalVelocity","targetVelocity","collisionEntities","floorEntity","keydownHandler","keyupHandler","mousemoveHandler","clickHandler","pointerlockchangeHandler","tmpVec","tmpVec2","right","createCollisionMeshes","loadPromises","customMeshUrl","promise","loadCustomCollisionMesh","push","addComponent","configureCollisionEntity","all","ext","assetType","asset","Asset","reject","ready","containerResource","resource","instantiateRenderEntity","renderEntity","children","addChild","computeAndStoreBounds","err","error","assets","load","bounds","BoundingBox","boundsInitialized","traverse","node","render","meshInstances","mi","aabb","child","pos","rot","scaling","setLocalScale","setEntityVisibility","root","setupInputHandlers","removeInputHandlers","pointerLockElement","exitPointerLock","e","code","movementX","movementY","requestPointerLock","removeEventListener","checkGround","floorPos","floorScale","highestGround","topY","moveX","moveZ","isSprinting","yawRad","sin","cos","speed","lerpFactor","pow","damp","lerp","currentPos","groundY","targetFeetY","collisionMeshEntities","grounded","getVelocity","_GsplatRevealRadialClass","getGsplatRevealRadialClass","GsplatRevealRadial","createScript","Object","assign","prototype","effectTime","_materialsApplied","_shadersApplied","_retryCount","_maxRetries","_materialCreatedHandler","_systemMaterialHandler","_centerArray","_dotTintArray","_waveTintArray","acceleration","delay","dotTint","waveTint","oscillationIntensity","endRadius","initialize","Set","Color","_applyShaders","_removeShaders","floor","toFixed","size","_isEffectComplete","_updateUniforms","_setUniform","_getCompletionTime","liftStartTime","discriminant","getShaderGLSL","getShaderWGSL","gsplatComponent","gsplat","gsplatRuntime","isUnified","unified","gsplatSystem","systems","material","layer","_applyShaderToMaterial","cameras","findComponents","layers","cameraComp","layerId","getLayerById","getGSplatMaterial","instance","_instance","_applyToInstance","layerList","miRuntime","gsplatInstance","_gsplatInstance","gsplatInst","checkEntity","runtimeEntity","inst","materials","_materials","materialsAsMC","Map","_material","has","methods","obj","names","getOwnPropertyNames","_error","getPrototypeOf","m","startsWith","join","glsl","wgsl","materialWithShaders","hasSetShaderChunk","setShaderChunk","chunks","gsplatEffectGLSL","gsplatEffectWGSL","shader","setParameter","clear","off","REVEAL_PRESETS","fast","medium","slow","getRevealPreset","preset","defineProperty","lib","loop","conditional","parse","stream","schema","result","arguments","parent","partSchema","conditionFunc","continueFunc","arr","lastStreamPos","newParent","uint8","readBits","readArray","readString","peekBytes","readBytes","readByte","buildStream","uint8Data","peekByte","offset","subarray","from","fromCharCode","readUnsigned","littleEndian","bytes","byteSize","totalOrFunc","total","parser","_byte","bits","reduce","res","def","startIndex","subBitsTotal","decompressFrames","decompressFrame","parseGIF","_gif","exports","_","require$$0","_uint","require$$1","subBlocksSchema","blocks","streamSize","availableSize","Uint8Array","gceSchema","gce","codes","extras","future","disposal","userInput","transparentColorGiven","transparentColorIndex","terminator","imageSchema","image","descriptor","left","top","lct","exists","interlaced","sort","minCodeSize","textSchema","blockSize","preData","applicationSchema","application","commentSchema","comment","_default","header","signature","version","lsd","gct","resolution","backgroundColorIndex","pixelAspectRatio","frames","nextCode","__esModule","default","_jsBinarySchemaParser","require$$2","_deinterlace","deinterlace_1","deinterlace","pixels","newPixels","rows","cpRow","toRow","fromRow","fromPixels","slice","splice","apply","concat","offsets","steps","pass","_lzw","lzw_1","lzw","pixelCount","available","code_mask","code_size","end_of_information","in_code","old_code","data_size","datum","first","pi","bi","MAX_STACK_SIZE","npix","dstPixels","prefix","suffix","pixelStack","arrayBuffer","byteData","buildImagePatch","totalPixels","resultImage","dims","colorTable","disposalType","transparentIndex","patch","patchData","Uint8ClampedArray","colorIndex","generatePatch","parsedGif","buildImagePatches","f","AnimatedGifTexture","currentFrameIndex","isLoaded","lastFrameTime","updateHandler","texture","gifWidth","gifHeight","drawFrame","ctx","getContext","willReadFrequently","buffer","gif","Texture","format","PIXELFORMAT_RGBA8","mipmaps","minFilter","FILTER_LINEAR","magFilter","addressU","ADDRESS_CLAMP_TO_EDGE","addressV","onReady","onError","frameIndex","prevFrame","clearRect","imageData","ImageData","tempCanvas","putImageData","drawImage","updateTexture","getImageData","lock","unlock","upload","stop","playing","loaded","HtmlMeshManager","meshes","useTexElement2D","GraphicsDevice","_isHTMLElementInterface","HTMLImageElement","HTMLCanvasElement","HTMLVideoElement","originalIsBrowserInterface","_isBrowserInterface","call","patchPlayCanvasForHtmlMesh","device","supportsTexElement2D","createMesh","htmlElement","createHtmlElement","createTexture","createMaterial","createEntity","destroyMesh","updateMeshTexture","animated","startUpdateLoop","pointerEvents","zIndex","overflow","css","html","setAttribute","visibility","body","setSource","renderToCanvas","svg","img","Image","blob","Blob","URL","createObjectURL","revokeObjectURL","onerror","fillStyle","fillRect","font","textAlign","fillText","StandardMaterial","diffuseMap","emissiveMap","emissive","opacity","blendType","BLEND_NORMAL","BLEND_NONE","cull","doubleSided","CULLFACE_NONE","CULLFACE_BACK","aspect","castShadows","receiveShadows","billboard","findComponent","lookAt","lastUpdate","rate","updateRate","last","delete","values","some","getMesh","updateVisibility","scrollPercent","waypointIndex","visibilityRange","start","end","billboardRange","billboardActive","_billboardActive","getAllMeshes","setupHtmlMeshes","manager","CustomScriptSystem","isInitialized","scriptCleanup","lastError","updateCallbacks","api","registerCleanup","fn","addCleanup","updateScript","execute","sanitizeScript","s","preprocessScript","processed","trim","cleanup","processedScript","cancelled","watchdog","requestAnimationFrame","wrappedApp","create","registerUpdate","callback","safeCallback","idx","indexOf","registerBeforeRender","pcNamespace","getScrollPercentage","getCurrentWaypointIndex","getHotspots","getSplats","getHTMLMeshes","func","Function","module","fakeRequire","blocked","Proxy","cleanupCandidate","bind","getLastError","dispose","FrameSequencePlayer","frameAssets","activeEntityIndex","currentFrame","loadingFrames","destroyed","isDisplaying","listeners","frameUrls","fps","preloadCount","frameInterval","createSplatEntity","preloadInitialFrames","autoplay","preloadFrame","then","framesToLoad","onLoadProgress","unloadFrame","unload","updatePreloadWindow","displayFrame","nextEntityIdx","currentEntity","nextEntity","emit","onFrameChange","elapsed","nextFrame","setFrame","previousFrame","getCurrentFrame","getTotalFrames","getProgress","setProgress","getFps","setFps","getIsPlaying","setLoop","getLoop","setScale","event","args","cb","EventEmitter","isMobileDevice","vendor","window","opera","LOD_PRESETS","lodDistances","desktop","mobile","isLodStreamingFormat","isLod","createViewer","lazyEvents","pendingButtonLabels","actualInstance","lazyLoadThumbnail","buttonText","thumbnailType","onStart","lazyLoadContainer","mediaType","lowerUrl","detectMediaType","startBtn","video","transition","createLazyLoadUI","setButtonLabels","goToWaypoint","getWaypointCount","getRotation","getCameraMode","setExploreMode","goToOriginalSplat","goToSplat","getCurrentSplatUrl","isShowingOriginalSplat","getAdditionalSplats","muteAll","unmuteAll","isMuted","triggerHotspot","closeHotspot","el","resize","navigateToScene","events","allowParentStyles","originalWidth","originalHeight","message","errorMessage","isOutdatedScene","showErrorPopup","isEditorMode","editor","showUI","editorSkipUI","uiOpts","currentButtonLabels","mapCameraMode","hasCollisionMeshesForWalk","allowedModes","a","defaultMode","uiElements","showScrollControls","showModeToggle","showFullscreenButton","showHelpButton","showPreloader","modeContainer","fullscreenBtn","waypointItemsHtml","toggleBtn","dropdown","stopPropagation","contains","vrBtn","vrButton","arBtn","arButton","helpBtn","hotspotPopup","portalPopup","watermark","finalLink","fpsCounter","createUIElements","baseGraphicsOptions","antialias","alpha","powerPreference","Application","graphicsDeviceOptions","Mouse","TouchDevice","keyboard","Keyboard","webgl2Error","preferWebGl2","webgl1Error","errorDiv","heading","preventDefault","setCanvasFillMode","FILLMODE_FILL_WINDOW","setCanvasResolution","RESOLUTION_AUTO","isMobile","lodPresetName","lodPreset","lodUpdateAngle","lodBehindPenalty","radialSorting","lodUpdateDistance","lodUnderfillLimit","lodRangeMin","lodRangeMax","colorUpdateDistance","colorUpdateAngle","colorUpdateDistanceLodScale","colorUpdateAngleLodScale","currentWaypointIndex","splatEntity","revealScript","isDestroyed","frameSequencePlayer","currentSplatUrl","isLoadingSplat","preloadedSplats","lastSplatCheckProgress","lastSplatCheckWaypointIndex","clearColor","finalRot","convertWaypointRotation","light","LIGHTTYPE_DIRECTIONAL","intensity","baseMoveSpeed","cameraControls","characterController","catch","currentCameraMode","waypointControlEnabled","picker","Picker","isInXR","xrSessionType","arContentEntity","arContentTexture","arContentVisible","hideARContent","fpsUpdateTimer","cameraPos","hotspotEntities","hotspotData","mediaTriggerMode","hotspotPos","isInProximity","proximityDistance","wasInProximity","videoElement","playVideoHotspot","audioElements","audio","paused","audioCtx","state","resume","pauseVideoHotspot","updateProximityTriggers","waypointAudioMap","audioData","audioId","slotId","assetReady","slot","sound","spatialSound","audioPos","maxDistance","stopOnExit","updateWaypointAudioProximity","audioEmitterMap","emitterData","emitterPos","updateAudioEmitterProximity","wpPos","triggerDist","activeWaypointTriggers","interaction","executeWaypointInteractions","reverseWaypointInteractions","checkWaypointTriggerDistance","panelPos","rotateLocal","updateARContentPosition","exploreBtns","updateExploreBtnState","activeMode","userYawOffset","userPitchOffset","isUserDragging","exploreDefault","targetCameraPosition","targetCameraRotation","pickerScale","canvasEl","clientWidth","clientHeight","worldLayer","getLayerByName","prepare","worldPoint","getWorldPointAsync","findBetterFocusPoint","updateProgress","loadingLabel","bar","textEl","percent","updatePreloaderProgress","isStorySplatHostedUrl","host","qx","_x","qy","_y","qz","_z","qw","_w","w","Quat","setFromEulerAngles","hideSplat","disposeSplat","showSplat","preloadSplat","Date","invertX","invertY","finalScale","finalRotDeg","applyExploreModeForSplat","splatExploreMode","isOriginal","targetMode","loadSwapSplat","swapIndex","findIndex","currentIndex","nextIndex","nextSplat","preloadNextSplat","getPrimarySplatUrl","updateSplats","numWaypoints","currentProgress","bestWaypointSplat","bestPercentageSplat","bestWaypointTrigger","bestPercentageTrigger","splat","bestSplat","isReturnToOriginal","primaryUrl","targetUrl","defaultExploreMode","skyboxAsset","TEXTURETYPE_RGBM","skyboxMip","rotQuat","applySkybox","targetProgress","isAnimatingProgress","totalDuration","sum","pathLength","playbackSpeed","rawLoopMode","playbackDirection","PULL_STRENGTH","lastPointerX","lastPointerY","waypointPositions","waypointRotations","waypointFOVs","defaultFOV","targetFOV","updateCameraFromProgress","segmentProgress","segmentIndex","t","startPos","endPos","startRot","endRot","slerp","startFOV","endFOV","newIndex","prevIndex","currentWaypoint","waypointCameraMode","orbitTarget","previousIndex","autoplayTriggered","isLeaving","stopAllHotspotAudio","stopHotspotPopupMedia","popupEl","animate","clampedProgress","animateToProgress","progressDiff","newFOV","userRotationOffset","targetWithUserOffset","mul2","currentRot","newRot","transitionDuration","animationTarget","startProgress","startTime","waypointProgress","buttonMode","scrollAmountSetting","newProgress","lastPlaybackTime","playbackAnimationId","playbackLoop","timestamp","deltaTime","progressIncrement","cancelAnimationFrame","scrollCanvas","scrollSpeedSetting","effectivePathLength","baseIncrement","deltaY","scrollIncrement","passive","clientX","clientY","capture","deltaX","portalEntities","portalPopupEl","pendingPortal","hidePortalPopup","confirmBtn","cancelBtn","portal","handlePortalNavigation","parseColor","allAudioContexts","allAudioElements","globalMuted","storedVolumes","currentTime","containerEl","iframe","particleEntities","particleTextures","PARTICLE_TEXTURE_URLS","flare","circle","spark","rain","smoke","loadParticleTexture","createParticleSystemEntity","psConfig","getPos","vec","defaultVal","getColor","emitterPosition","color1","color2","colorDead","direction1","direction2","lifetime","minLifeTime","maxLifeTime","emitterShape","EMITTERSHAPE_BOX","emitterExtents","emitterOffset","emitterType","EMITTERSHAPE_SPHERE","emitterRadius","emitBoxMin","emitBoxMax","boxMin","boxMax","blendMode","BLEND_ADDITIVEALPHA","blendModeStr","BLEND_MULTIPLICATIVE","BLEND_ADDITIVE","gravityX","gravityY","gravityZ","endVelX","endVelY","endVelZ","minAngularSpeed","maxAngularSpeed","angularSpeed","minInitialRotation","maxInitialRotation","minSize","maxSize","minScaleX","maxScaleX","minScaleY","maxScaleY","minEmitPower","maxEmitPower","avgDirX","avgDirY","avgDirZ","numParticles","emitRate","startAngle","startAngle2","radialSpeedGraph","Curve","localVelocityGraph","CurveSet","localVelocityGraph2","velocityGraph","scaleGraph","scaleGraph2","rotationSpeedGraph","rotationSpeedGraph2","colorGraph","alphaGraph","blend","depthWrite","depthSoftening","softParticles","lighting","halfLambert","alignToMotion","stretch","preWarm","orientation","particlesystem","localSpace","translationPivotX","translationPivot","translationPivotY","renderingGroupId","drawOrder","customMeshEntities","playAnimComponentV2","animInfo","component","animations","clips","modelEntity","animList","firstAnim","_name","_duration","anim","stateGraphData","states","transitions","to","time","conditions","activate","animComp","loadStateGraph","animAsset","assignAnimation","animComponent","loopedTime","_curves","curve","paths","_paths","entityPath","findByPath","findByName","evaluate","prop","propertyPath","setLocalPosition","setLocalRotation","baseLayer","playableState","pauseAnimComponentV2","loadCustomMesh","meshConfig","modelUrl","radToDeg","modelAsset","meshId","meshData","isAnimPlaying","audioPlaying","enableAllChildren","childCount","opacityMode","applyOpacityToCustomMesh","originalRotation","camPos","meshPos","angle","atan2","hasGlbAnimations","hasAnimations","animationCount","interactionConfig","playModelAnimation","allAnimComponents","findAllAnimComponents","depth","animation","animationNames","shouldAutoPlay","animationAutoPlay","playAudio","audioUrl","audioConfig","positional","audioSpatial","distanceModel","audioDistanceModel","refDistance","audioRefDistance","audioMaxDistance","rollOffFactor","audioRolloffFactor","slots","audioLoop","volume","audioVolume","audioSlotId","audioAsset","setupMeshAudio","customMesh","triggerUIPopup","triggerDirectLink","hasInteractions","canvasForMesh","performRaycast","rect","getBoundingClientRect","dir","sub2","rayIntersectsMeshHierarchy","dot","add2","popupTriggerMode","audioTriggerMode","animationTriggerMode","directLinkTriggerMode","isHovering","handleHoverIn","cursor","displayMeshContent","handleDirectLink","handleHoverOut","meshPopup","hideMeshContent","handleClick","meshClickHandler","meshHoverHandler","nowHovering","meshLeaveHandler","setupMeshClickInteraction","opacityConfig","rayOrigin","rayDir","rayIntersectsAABB","model","getMin","getMax","tmin","tmax","axes","readAxis","byAxis","byUnderscore","origin","minVal","maxVal","t1","t2","existingPopup","popupContent","content","onclick","showMeshPopup","directLinkUrl","open","applyToEntity","ent","meshInstance","_isCloned","cloned","BLEND_PREMULTIPLIED","depthTest","alphaTest","cleanupCustomMeshes","canvasForCleanup","opacityAnimation","startPercent","endPercent","startOpacity","endOpacity","bRange","updateCustomMeshVisibility","currentSkyboxEntity","skyboxFollowCameraHandler","initSkybox","skyboxIntensity","enableIBL","isHDR","skyboxEntity","skyboxMaterial","useLighting","textureAsset","FILTER_LINEAR_MIPMAP_LINEAR","exposure","toneMapping","TONEMAP_ACES","ambientLight","iblError","rotationDegrees","lightEntities","hexToColorForLights","exec","createPointLight","lightConfig","LIGHTTYPE_POINT","createDirectionalLightEntity","createHemisphericLight","groundColor","skyColor","createAmbientLight","createSpotLight","angleDeg","exponent","innerAngleDeg","innerConeAngle","innerAngle","outerAngleDeg","outerConeAngle","outerAngle","LIGHTTYPE_SPOT","shadowBias","normalOffsetBias","direction","dirVec","getPlayCanvasDistanceModel","modelStr","animatedGifs","createImageMaterial","imageUrl","onTextureReady","twoSidedLighting","diffuse","specular","lower","isGifUrl","gifTexture","premultiplyAlpha","opacityMap","opacityMapChannel","crossOrigin","createSingleHotspot","rawScale","scaleObj","sz","rotX","rotY","rotZ","sphereOpacity","initialOpacity","targetOpacity","textureLoaded","hiddenUntilTextureLoaded","shouldBeVisible","hotspotMaterial","videoUrl","useAlphaMethod","useIOSVideoAlphaMethod","forceIOSVideoAlphaMethodForAllDevices","mainVideoUrl","iosMainVideoUrl","alphaVideoUrl","alphaMaskVideoUrl","hasWebMAlpha","isWebMUrl","webmHasAlpha","createVideoAndTexture","isAlpha","useRGBA","videoLoop","playsInline","muted","videoMuted","PIXELFORMAT_R8_G8_B8_A8","PIXELFORMAT_R8_G8_B8","main","videoTexture","videoBackupUrl","backupUrl","alphaVideo","alphaTexture","readyState","HAVE_ENOUGH_DATA","initialScale","videoWidth","videoHeight","ratio","alphaVideoElement","isVideoPlaying","videoSpatialAudio","AudioContext","webkitAudioContext","source","createMediaElementSource","panner","createPanner","panningModel","videoDistanceModel","videoRefDistance","videoMaxDistance","rolloffFactor","videoRolloffFactor","connect","destination","camForward","camUp","up","listener","positionX","positionY","positionZ","forwardX","forwardY","forwardZ","upX","upY","upZ","setOrientation","setupVideoSpatialAudio","gifUrl","loadAnimatedGif","gifCanvas","animateGif","gifAnimationFrame","fallbackMaterial","updateAudioPosition","setupHotspotAudio","hasBillboardRange","billboardRangeStart","billboardRangeEnd","_billboardOriginalRotation","updateHotspotVisibility","alwaysVisible","rangeStart","rangeEnd","origRot","triggerMode","createSinglePortal","rawPortalScale","portalScaleObj","portalMaterial","targetSceneName","HAVE_CURRENT_DATA","portalData","targetSceneId","updatePortalVisibility","portalPos","proximityTriggered","raycastPortals","closestHit","portalId","sceneName","existing","getComputedStyle","loadingDiv","spinner","inset","background","flexDirection","alignItems","justifyContent","border","borderTop","borderRadius","marginBottom","styleEl","showPortalLoadingUI","sceneApiUrl","status","newSceneData","updatePortalLoadingText","portalHtmlMgr","__htmlMeshManager","portalScriptSys","__customScriptSystem","cleanupForPortalNavigation","parentNode","hidePortalLoadingUI","padding","clickSuppressed","pointerDown","pointerStartX","pointerStartY","clearPointerDown","raycastHotspots","currentHoverHotspot","isMouseOverPopup","overlay","handleDoubleClickFocus","screenX","screenY","hotspotHit","scaledX","scaledY","getSelectionAsync","aabbCenter","portalHit","effectiveActivationMode","hit","overlayEl","confirmNavigation","showPortalPopup","hitResult","hasPopupContent","canvasWidth","canvasHeight","bgColor","strokeStyle","lineWidth","strokeRect","currentY","maxWidth","lineHeight","words","line","word","testLine","measureText","wrapText","showARContent","teleportWaypoint","teleportToWaypoint","teleportPercent","teleportToPercent","teleportMode","calculatedTargetProgress","targetIndex","lastTapTime","changedTouches","loadContent","bandwidthUsed","isStorySplatHosted","loadedUrl","urls","isLodFormat","urlIsSogFormat","extension","isLodStreaming","isSogFormat","received","loadProgress","hasRejected","unhandledRejectionHandler","errorMsg","reason","details","gs","distances","userRotation","revealPresetName","revealEffect","revealConfig","GsplatRevealRadialClass","syncError","errObj","statusCode","handler","loader","getHandler","octreeParser","parsers","octree","urlObj","isHostedOnStorySplat","lodEnabled","bandwidthCounted","audioEntity","soundConfig","audioDataRef","emitters","emitter","random","toString","substr","addSlot","overlap","setupAudioEmitters","ps","textureName","particleTexture","textureUrl","textureCacheKey","customTextureUrl","colorMap","safeName","stack","initParticleSystems","loadedCount","skippedCount","hasModelUrl","initCustomMeshes","isAvailable","XRTYPE_VR","XRTYPE_AR","startXr","XRSPACE_LOCALFLOOR","setupXR","htmlMeshManager","customScriptSystem","scriptSystem","setupCustomScript","urlParams","URLSearchParams","location","search","waypointParam","autoplayParam","isNaN","popupCloseBtn","onWaypointClick","items","setupWaypointListClickHandlers","handleResize","resizeCanvas","editorHotspotMap","editorLightMap","editorParticleMap","editorCustomMeshMap","editorPortalMap","getFrameProgress","setFrameProgress","splatConfig","currentUrl","destroyScriptSys","destroyHtmlMgr","_storedVolume","storedVol","h","gl","modeBtn","orbitBtn","flyBtn","wpToggle","fsBtn","addEl","tag","bold","addSpacer","editorInstance","getCameraControls","getApp","getSplatEntity","getAllHotspotEntities","getAllLightEntities","getCamera","addHotspot","removeHotspot","updateHotspot","addLight","lightCfg","removeLight","updateLight","addCustomMesh","meshCfg","removeCustomMesh","addParticleSystem","removeParticleSystem","addPortal","removePortal","videoEl","updatePortal","addHtmlMesh","htmlContent","removeHtmlMesh","addCollisionMesh","px","py","pz","rx","ry","rz","renderType","_collisionMeshId","removeCollisionMesh","addAudioEmitter","emitterConfig","removeAudioEmitter","updateAudioEmitter","setSkybox","setSkyboxRuntime","setBackgroundColor","pcColor","setFOV","newFov","addWaypoint","removeWaypoint","updateWaypoint","rebuildTourPath","createViewerFromUrl","trackEmbedUsage","baseUrl","ownerId","method","headers","SceneNotFoundError","super","SceneApiError","DEFAULT_BASE_URL","process","env","STORYSPLAT_API_URL","browserWindow","__STORYSPLAT_API_URL__","getDefaultBaseUrl","createViewerFromSceneId","apiUrl","encodeURIComponent","apiKey","errorText","apiResponse","success","meta","_baseUrl","_apiKey","viewerOptions","bandwidthTracked","fetchSceneMeta","userSlug","GizmoManager","translateGizmo","rotateGizmo","scaleGizmo","activeGizmo","attachedEntity","gizmoLayer","Gizmo","createLayer","TranslateGizmo","RotateGizmo","ScaleGizmo","setSnap","snap","snapIncrement","setCoordSpace","coordSpace","setupEvents","gizmo","onTransformStart","onTransformMove","onTransformEnd","attachToMesh","mesh","positionGizmoEnabled","rotationGizmoEnabled","scaleGizmoEnabled","increment","space","onTransform","callbacks","SelectionManager","selectedEntities","highlightedEntity","originalMaterials","highlightColor","multiSelect","pickAtScreenPosition","pickerWidth","pickerHeight","pickerX","pickerY","selection","getSelection","tags","getWorldPointAtScreen","select","additive","previousEntity","getSelectedEntity","deselectAll","applyHighlight","onSelectionChange","deselect","removeHighlight","isSelected","getSelectedEntities","materialMap","highlightMaterial","emissiveIntensity","originalMaterial","onSelect","handlePointerDown","MouseEvent","EditorCameraController","tourCamera","fpvEntity","_enabled","editCamera","tourCam","showFPVVisualization","createFPVVisualization","tourPos","tourRot","focusOn","dist","orbitDistance","syncClearColor"],"mappings":"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","x_google_ignoreList":[7,8,9,10,11,12]}
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../src/html-generation/generateHTML.ts","../src/transformers/sceneToConfig.ts","../src/dynamic-viewer/viewerUI.ts","../src/dynamic-viewer/CameraControls.ts","../src/dynamic-viewer/CharacterController.ts","../src/effects/gsplat-reveal-radial.ts","../src/effects/reveal-presets.ts","../node_modules/js-binary-schema-parser/lib/index.js","../node_modules/js-binary-schema-parser/lib/parsers/uint8.js","../node_modules/gifuct-js/lib/index.js","../node_modules/js-binary-schema-parser/lib/schemas/gif.js","../node_modules/gifuct-js/lib/deinterlace.js","../node_modules/gifuct-js/lib/lzw.js","../src/dynamic-viewer/AnimatedGifHelper.ts","../src/dynamic-viewer/HtmlMeshHelper.ts","../src/dynamic-viewer/CustomScriptSystem.ts","../src/dynamic-viewer/FrameSequencePlayer.ts","../src/dynamic-viewer/createViewer.ts","../src/dynamic-viewer/createViewerFromSceneId.ts","../src/editor/GizmoManager.ts","../src/editor/SelectionManager.ts","../src/editor/EditorCameraController.ts"],"sourcesContent":["/**\n * Simple HTML Generator for StorySplat\n *\n * Generates standalone HTML that loads the viewer from CDN.\n * This replaces the old 13K+ line HTML generation system with a simple\n * bootstrap HTML that uses the same viewer code as dynamic embedding.\n */\n\nimport type { SceneData } from '../types';\n\nexport interface GenerateHTMLOptions {\n /** Custom CDN URL for the viewer bundle. Default: unpkg */\n cdnUrl?: string;\n /** HTML page title. Default: scene name */\n title?: string;\n /** Meta description for SEO */\n description?: string;\n /** Favicon URL */\n faviconUrl?: string;\n /** Custom CSS to inject */\n customCSS?: string;\n /** Whether to minify the output */\n minify?: boolean;\n /** Enable lazy loading - shows thumbnail with start button before loading (overrides scene data) */\n lazyLoad?: boolean;\n /** Custom button text for lazy loading (overrides scene data) */\n lazyLoadButtonText?: string;\n}\n\n/**\n * Escape HTML entities in a string\n */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Escape string for safe embedding in inline <script> tags.\n * Prevents XSS by escaping </script> sequences that would close the script tag prematurely.\n */\nfunction escapeForInlineScript(str: string): string {\n // Escape </script> (case-insensitive) to prevent script tag injection\n // Using Unicode escapes for the < character within script context\n return str.replace(/<\\/script/gi, '<\\\\/script');\n}\n\n/**\n * Generate standalone HTML for a StorySplat scene\n *\n * @param sceneData - Scene data object (same format as createViewer)\n * @param options - Generation options\n * @returns Complete HTML string ready to be saved as .html file\n *\n * @example\n * ```typescript\n * import { generateHTML } from 'storysplat-viewer';\n *\n * const html = generateHTML(sceneData, { title: 'My Scene' });\n * // Save html to file or serve it\n * ```\n */\nexport function generateHTML(sceneData: SceneData, options: GenerateHTMLOptions = {}): string {\n const {\n cdnUrl = 'https://unpkg.com/storysplat-viewer@2/dist/storysplat-viewer.umd.js',\n title = sceneData.name || 'StorySplat Scene',\n description = `Interactive 3D scene: ${sceneData.name || 'StorySplat'}`,\n faviconUrl,\n customCSS = '',\n lazyLoad: optionsLazyLoad,\n lazyLoadButtonText: optionsLazyLoadButtonText\n } = options;\n\n // Resolve lazy load settings (options override scene data)\n const lazyLoad = optionsLazyLoad ?? sceneData.uiOptions?.lazyLoad ?? false;\n const lazyLoadButtonText = optionsLazyLoadButtonText ?? sceneData.uiOptions?.lazyLoadButtonText;\n\n // PlayCanvas CDN URL - loaded before viewer bundle (UMD expects 'pc' global)\n const playcanvasCdnUrl = 'https://cdn.jsdelivr.net/npm/playcanvas@2.14.3/build/playcanvas.min.js';\n\n const faviconTag = faviconUrl\n ? `<link rel=\"icon\" href=\"${escapeHtml(faviconUrl)}\" />`\n : '';\n\n const customCSSBlock = customCSS\n ? `<style>${customCSS}</style>`\n : '';\n\n // Serialize scene data, handling circular references\n // Then escape for safe embedding in inline <script> tags\n const rawJSON = JSON.stringify(sceneData, (key, value) => {\n // Skip any DOM nodes or circular refs\n // Check for HTMLElement safely (it doesn't exist in Node.js)\n if (typeof HTMLElement !== 'undefined' && value instanceof HTMLElement) return undefined;\n if (typeof value === 'function') return undefined;\n // Skip any object with nodeType (DOM nodes)\n if (value && typeof value === 'object' && 'nodeType' in value) return undefined;\n return value;\n }, 2);\n // Escape </script> sequences to prevent XSS attacks\n const sceneDataJSON = escapeForInlineScript(rawJSON);\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n <meta name=\"description\" content=\"${escapeHtml(description)}\">\n <title>${escapeHtml(title)}</title>\n ${faviconTag}\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body {\n width: 100%;\n /* Use 100dvh for iOS address bar support, with 100vh fallback */\n height: 100vh;\n height: 100dvh;\n overflow: hidden;\n background: #111;\n }\n #app {\n width: 100%;\n /* Use 100dvh for iOS address bar support, with 100vh fallback */\n height: 100vh;\n height: 100dvh;\n position: relative;\n }\n /* Loading state */\n #app:empty::after {\n content: 'Loading...';\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: #666;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 16px;\n }\n </style>\n ${customCSSBlock}\n</head>\n<body>\n <div id=\"app\"></div>\n\n <script src=\"${playcanvasCdnUrl}\"></script>\n <script src=\"${escapeHtml(cdnUrl)}\"></script>\n <script>\n (function() {\n 'use strict';\n\n var sceneData = ${sceneDataJSON};\n\n // Wait for DOM and viewer to be ready\n function init() {\n var container = document.getElementById('app');\n if (!container) {\n console.error('[StorySplat] Container #app not found');\n return;\n }\n\n if (typeof StorySplatViewer === 'undefined' || !StorySplatViewer.createViewer) {\n console.error('[StorySplat] Viewer not loaded. Check CDN URL.');\n return;\n }\n\n try {\n var viewer = StorySplatViewer.createViewer(container, sceneData, {\n showUI: true,\n autoPlay: false,\n lazyLoad: ${lazyLoad},\n lazyLoadButtonText: ${lazyLoadButtonText ? `'${escapeForInlineScript(lazyLoadButtonText)}'` : 'undefined'}\n });\n\n viewer.on('ready', function() {\n console.log('[StorySplat] Scene ready');\n });\n\n viewer.on('error', function(err) {\n console.error('[StorySplat] Error:', err);\n });\n\n // Expose viewer globally for debugging\n window.storySplatViewer = viewer;\n } catch (err) {\n console.error('[StorySplat] Failed to create viewer:', err);\n }\n }\n\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', init);\n } else {\n init();\n }\n })();\n </script>\n</body>\n</html>`;\n}\n\n/**\n * Generate HTML from a scene URL (fetches the JSON first)\n *\n * @param jsonUrl - URL to fetch scene JSON from\n * @param options - Generation options\n * @returns Complete HTML string\n */\nexport async function generateHTMLFromUrl(\n jsonUrl: string,\n options: GenerateHTMLOptions = {}\n): Promise<string> {\n const response = await fetch(jsonUrl);\n if (!response.ok) {\n throw new Error(`Failed to fetch scene: ${response.statusText}`);\n }\n const sceneData: SceneData = await response.json();\n return generateHTML(sceneData, options);\n}\n","/**\n * Scene Data to ExportProps Transformer\n *\n * Converts Firebase scene JSON or direct scene data to the ExportProps format\n * used by the HTML generation system.\n */\nimport type { SceneData, WaypointData, ExportProps, SplatSwapPoint, HotspotData, PortalData, CustomMeshConfig, HtmlMeshConfig, LightConfig, ParticleConfig, CollisionMeshConfig, AudioEmitterConfig, TemplateType } from '../types';\ntype InteractionDataWithText = {\n text?: unknown;\n};\nfunction getInteractionText(data: unknown): string | undefined {\n if (!data || typeof data !== 'object')\n return undefined;\n const maybe = data as InteractionDataWithText;\n return typeof maybe.text === 'string' ? maybe.text : undefined;\n}\n/**\n * Transform scene data to ExportProps format\n */\nexport function transformSceneToExportProps(scene: SceneData): ExportProps {\n // Get all available URLs\n const originalUrl = scene.loadedModelUrl || scene.splatUrl || '';\n const sogUrl = scene.sogModelUrl || scene.sogUrl;\n const compressedPlyUrl = scene.compressedPlyUrl;\n const lodMetaUrl = scene.lodMetaUrl; // LOD streaming format (highest priority)\n // Skybox: prefer the new nested format (`scene.skybox`), but support legacy fields used by the editor.\n // This ensures older stored scene JSON still renders a skybox in the viewer.\n const legacySkyboxUrl = (typeof scene.activeSkyboxUrl === 'string' && scene.activeSkyboxUrl) ? scene.activeSkyboxUrl :\n (typeof scene.skyboxUrl === 'string' && scene.skyboxUrl) ? scene.skyboxUrl :\n undefined;\n const resolvedSkybox = scene.skybox || (legacySkyboxUrl ? {\n url: legacySkyboxUrl,\n rotation: scene.skyboxRotation\n } : undefined);\n // For PlayCanvas, prefer formats in this order: LOD > SOG > Compressed PLY > PLY > Original\n // PlayCanvas can't directly load .splat files, so we need PLY-compatible formats\n const originalExt = originalUrl.split('?')[0].split('.').pop()?.toLowerCase();\n const isPlayCanvasCompatible = originalExt === 'ply' || originalUrl.includes('.compressed.ply');\n // Choose the best URL for PlayCanvas (for backward compatibility, splatUrl is the main URL)\n // LOD streaming is handled separately as lodMetaUrl\n let splatUrl = originalUrl;\n if (sogUrl) {\n splatUrl = sogUrl; // SOG is best (after LOD)\n }\n else if (compressedPlyUrl) {\n splatUrl = compressedPlyUrl; // Compressed PLY is second best\n }\n else if (!isPlayCanvasCompatible) {\n // Original is not compatible, keep it but warn\n console.warn('[StorySplat Viewer] Original file format may not be compatible with PlayCanvas:', originalExt);\n }\n console.log('[StorySplat Viewer] URL selection:', {\n original: originalUrl,\n lodMetaUrl,\n sogUrl,\n compressedPlyUrl,\n selected: splatUrl\n });\n // Transform waypoints\n const waypoints = (scene.waypoints || []).map((wp: WaypointData) => {\n // Handle FOV - convert from radians to degrees if necessary\n // (matching waypointNavigation.ts logic)\n let fov = wp.fov || 60;\n if (fov < 3.5) {\n // FOV is in radians, convert to degrees\n fov = fov * (180 / Math.PI);\n }\n // Extract info from interactions (type: 'info') if not directly on waypoint\n // In SceneTypes.ts, waypoint info is stored in interactions array with type 'info'\n let info = wp.info || wp.description || '';\n if (!info && wp.interactions) {\n const infoInteraction = wp.interactions.find((i) => i.type === 'info');\n const text = getInteractionText(infoInteraction?.data);\n if (text) {\n info = text;\n }\n }\n return {\n position: wp.position || { x: wp.x || 0, y: wp.y || 0, z: wp.z || 0 },\n rotation: wp.rotation || { x: 0, y: 0, z: 0 },\n fov,\n duration: wp.duration || 2000,\n name: wp.name || wp.title || '',\n info,\n interactions: wp.interactions || [],\n triggerDistance: wp.triggerDistance ?? 1.0\n };\n });\n // Backward-compatible particle fallback:\n // prefer particleSystems only when it actually contains entries, otherwise use legacy particles.\n const resolvedParticles = Array.isArray(scene.particleSystems) && scene.particleSystems.length > 0\n ? scene.particleSystems\n : (scene.particles || []);\n // Build ExportProps\n const exportProps: ExportProps = {\n // Scene Metadata\n name: scene.name || 'StorySplat Scene',\n sceneId: scene.sceneId,\n userId: scene.userId,\n userName: scene.userName || 'Unknown',\n thumbnailUrl: scene.thumbnailUrl,\n // Splat Configuration\n splatUrl,\n sogUrl,\n lodMetaUrl,\n // Build fallback URLs from all available formats (excluding the primary)\n fallbackUrls: [\n ...(scene.fallbackUrls || []),\n // Add other available formats as fallbacks\n sogUrl !== splatUrl ? sogUrl : null,\n compressedPlyUrl !== splatUrl ? compressedPlyUrl : null,\n originalUrl !== splatUrl ? originalUrl : null\n ].filter((url): url is string => url != null && url !== ''),\n // Handle both splatScale (number) and scale (object) formats\n scale: scene.scale || (scene.splatScale != null\n ? { x: scene.splatScale, y: scene.splatScale, z: scene.splatScale }\n : { x: 1, y: 1, z: 1 }),\n // Handle both array and object position formats\n splatPosition: scene.splatPosition || scene.position || scene.cameraPosition || [0, 0, 0],\n splatRotation: scene.splatRotation || scene.rotation || scene.cameraRotation || [0, 0, 0],\n invertXScale: scene.invertXScale,\n invertYScale: scene.invertYScale,\n // Scene Data\n waypoints,\n hotspots: scene.hotspots || [],\n portals: scene.portals || [],\n // Prefer nested skybox, but keep flat properties populated for compatibility.\n skybox: resolvedSkybox,\n skyboxUrl: resolvedSkybox?.url,\n skyboxRotation: resolvedSkybox?.rotation,\n customMeshes: scene.customMeshes || [],\n htmlMeshes: scene.htmlMeshes || [],\n lights: scene.lights || [],\n particles: resolvedParticles,\n collisionMeshesData: scene.collisionMeshesData || [],\n playerHeight: scene.playerHeight,\n // UI Configuration\n uiColor: scene.uiColor || '#ffffff',\n uiOptions: {\n showStartExperience: scene.uiOptions?.showStartExperience ?? true,\n showWatermark: scene.uiOptions?.showWatermark ?? true,\n hideFullscreenButton: scene.uiOptions?.hideFullscreenButton ?? false,\n hideInfoButton: scene.uiOptions?.hideInfoButton ?? false,\n hideMuteButton: scene.uiOptions?.hideMuteButton ?? false,\n hideHelpButton: scene.uiOptions?.hideHelpButton ?? false,\n hideNavigator: scene.uiOptions?.hideNavigator ?? false,\n showWaypointList: scene.uiOptions?.showWaypointList ?? true,\n hideWatermark: scene.uiOptions?.hideWatermark ?? false,\n watermarkText: scene.uiOptions?.watermarkText,\n watermarkLink: scene.uiOptions?.watermarkLink,\n buttonPosition: scene.uiOptions?.buttonPosition || 'inline',\n buttonLabels: scene.uiOptions?.buttonLabels,\n // Whitelabeling option for custom preloader logo\n customPreloaderLogoUrl: scene.uiOptions?.customPreloaderLogoUrl,\n // Lazy loading options\n lazyLoad: scene.uiOptions?.lazyLoad,\n lazyLoadButtonText: scene.uiOptions?.lazyLoadButtonText,\n lazyLoadThumbnailUrl: scene.uiOptions?.lazyLoadThumbnailUrl,\n lazyLoadThumbnailType: scene.uiOptions?.lazyLoadThumbnailType,\n // Layout type\n uiType: scene.uiOptions?.uiType,\n // Debug mode (FPS counter)\n debugMode: scene.uiOptions?.debugMode\n },\n // Camera Configuration\n defaultCameraMode: scene.defaultCameraMode || 'orbit',\n allowedCameraModes: scene.allowedCameraModes || ['orbit', 'first-person', 'drone'],\n cameraMovementSpeed: scene.cameraMovementSpeed,\n cameraRotationSensitivity: scene.cameraRotationSensitivity,\n cameraDamping: scene.cameraDamping,\n invertCameraRotation: scene.invertCameraRotation,\n // Navigation Configuration\n includeScrollControls: scene.includeScrollControls ?? true,\n scrollButtonMode: scene.scrollButtonMode || 'continuous',\n scrollAmount: scene.scrollAmount || 100,\n scrollSpeed: scene.scrollSpeed,\n transitionSpeed: scene.transitionSpeed,\n autoPlayEnabled: scene.autoPlayEnabled ?? false,\n // Playback settings\n autoplaySpeed: scene.autoplaySpeed,\n loopMode: scene.loopMode,\n // XR Configuration\n includeXR: scene.includeXR,\n xrMode: scene.xrMode,\n // Custom Script\n customScript: scene.customScript,\n // Template type (read from scene uiOptions, default to minimal)\n templateType: (scene.uiOptions?.uiType as TemplateType) || 'minimal',\n // Splat Swap System\n additionalSplats: scene.additionalSplats || [],\n keepMeshesInMemory: scene.keepMeshesInMemory ?? false,\n initialSplatExploreMode: scene.initialSplatExploreMode,\n // Audio emitters\n audioEmitters: scene.audioEmitters || [],\n // 4DGS frame sequence\n frameSequence: scene.frameSequence\n };\n return exportProps;\n}\n/**\n * Viewer configuration for dynamic viewer runtime\n */\nexport interface ViewerConfig {\n splatUrl: string;\n sogUrl?: string;\n /** LOD streaming format URL (lod-meta.json) - highest priority for loading */\n lodMetaUrl?: string;\n fallbackUrls?: string[];\n scale?: {\n x: number;\n y: number;\n z: number;\n };\n position: number[];\n rotation: number[];\n invertXScale?: boolean;\n invertYScale?: boolean;\n waypoints?: WaypointData[];\n hotspots?: HotspotData[];\n portals?: PortalData[];\n /** Nested skybox config (newer format) */\n skybox?: {\n url: string;\n rotation?: number;\n intensity?: number;\n enableIBL?: boolean;\n };\n /** Flat skybox URL (older format, for backwards compatibility) */\n skyboxUrl?: string;\n /** Flat skybox rotation in radians (older format, for backwards compatibility) */\n skyboxRotation?: number;\n customMeshes?: CustomMeshConfig[];\n htmlMeshes?: HtmlMeshConfig[];\n lights?: LightConfig[];\n particles?: ParticleConfig[];\n collisionMeshesData?: CollisionMeshConfig[];\n cameraMode?: string;\n defaultCameraMode?: string;\n allowedCameraModes?: string[];\n autoPlay?: boolean;\n uiColor?: string;\n uiOptions?: SceneData['uiOptions'];\n // Camera settings (matching HTML export CONFIG)\n fov?: number;\n nearClip?: number;\n farClip?: number;\n playerHeight?: number;\n cameraMovementSpeed?: number;\n cameraRotationSensitivity?: number;\n cameraDamping?: number;\n invertCameraRotation?: boolean;\n // Navigation settings\n scrollSpeed?: number;\n scrollAmount?: number;\n scrollButtonMode?: 'continuous' | 'incremental' | 'percentage' | 'waypoint';\n transitionSpeed?: number;\n // Playback settings\n /** Speed of autoplay in progress per second (default: calculated from waypoint durations) */\n autoplaySpeed?: number;\n /** Loop behavior for playback: 'loop' restarts at beginning, 'pingpong' reverses direction, 'none' stops at end */\n loopMode?: 'loop' | 'pingpong' | 'none';\n // Splat Swap System\n additionalSplats?: SplatSwapPoint[];\n keepMeshesInMemory?: boolean;\n /** Default explore sub-mode for the initial splat. 'orbit' for aerial views, 'fly' for interior POV. */\n initialSplatExploreMode?: 'orbit' | 'fly';\n // XR Configuration\n includeXR?: boolean;\n xrMode?: 'ar' | 'vr' | 'both';\n // Custom Script\n customScript?: string;\n // Background color\n backgroundColor?: string;\n // Audio emitters\n audioEmitters?: AudioEmitterConfig[];\n // 4DGS frame sequence\n frameSequence?: {\n frameUrls: string[];\n fps?: number;\n loop?: boolean;\n preloadCount?: number;\n autoplay?: boolean;\n };\n}\n/**\n * Transform ExportProps to scene config for dynamic viewer\n * This is a simpler format for runtime use\n */\nexport function exportPropsToViewerConfig(props: ExportProps): ViewerConfig {\n // Get FOV from first waypoint if available, otherwise default to 60\n const fov = props.waypoints?.[0]?.fov || 60;\n return {\n splatUrl: props.splatUrl,\n sogUrl: props.sogUrl,\n lodMetaUrl: props.lodMetaUrl,\n fallbackUrls: props.fallbackUrls,\n scale: props.scale,\n position: props.splatPosition || [0, 0, 0],\n rotation: props.splatRotation || [0, 0, 0],\n invertXScale: props.invertXScale,\n invertYScale: props.invertYScale,\n waypoints: props.waypoints,\n hotspots: props.hotspots,\n portals: props.portals,\n skybox: props.skybox,\n skyboxUrl: props.skyboxUrl,\n skyboxRotation: props.skyboxRotation,\n customMeshes: props.customMeshes,\n htmlMeshes: props.htmlMeshes,\n lights: props.lights,\n particles: props.particles,\n collisionMeshesData: props.collisionMeshesData,\n cameraMode: props.defaultCameraMode,\n defaultCameraMode: props.defaultCameraMode,\n allowedCameraModes: props.allowedCameraModes,\n autoPlay: props.autoPlayEnabled,\n uiColor: props.uiColor,\n uiOptions: props.uiOptions,\n // Camera settings (matching HTML export CONFIG)\n fov: fov,\n nearClip: props.minClipPlane || 0.1,\n farClip: props.maxClipPlane || 1000,\n playerHeight: props.playerHeight || 1.6,\n cameraMovementSpeed: props.cameraMovementSpeed || 1,\n cameraRotationSensitivity: props.cameraRotationSensitivity || 0.2,\n cameraDamping: props.cameraDamping || 0.75,\n invertCameraRotation: props.invertCameraRotation,\n // Navigation settings\n scrollSpeed: props.scrollSpeed,\n scrollAmount: props.scrollAmount,\n scrollButtonMode: props.scrollButtonMode,\n transitionSpeed: props.transitionSpeed,\n // Playback settings\n autoplaySpeed: props.autoplaySpeed,\n loopMode: props.loopMode,\n // Splat Swap System\n additionalSplats: props.additionalSplats,\n keepMeshesInMemory: props.keepMeshesInMemory ?? false,\n initialSplatExploreMode: props.initialSplatExploreMode,\n // XR Configuration\n includeXR: props.includeXR,\n xrMode: props.xrMode,\n // Custom Script\n customScript: props.customScript,\n // Audio emitters\n audioEmitters: props.audioEmitters || [],\n // 4DGS frame sequence\n frameSequence: props.frameSequence\n };\n}\n","/**\n * Dynamic Viewer UI Module\n *\n * Generates and injects UI elements and CSS for the dynamic viewer.\n * Uses MINIMAL theme for embedded viewers.\n */\nimport type { ViewerConfig, ButtonLabels, WaypointData, HotspotData } from '../types';\ninterface ViewerUIController {\n nextWaypoint: () => void;\n prevWaypoint: () => void;\n play: () => void;\n pause: () => void;\n isPlaying: () => boolean;\n getCurrentWaypointIndex: () => number;\n getWaypointCount: () => number;\n getWaypoints?: () => WaypointData[];\n setCameraMode?: (mode: string) => void;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n on: (event: string, callback: (...args: any[]) => void) => void;\n}\ninterface WebkitFullscreenDocument extends Document {\n webkitFullscreenElement?: Element | null;\n webkitExitFullscreen?: () => Promise<void> | void;\n}\ninterface WebkitFullscreenElement extends HTMLElement {\n webkitRequestFullscreen?: () => Promise<void> | void;\n}\n/**\n * Default button labels for internationalization (i18n)\n */\nexport const DEFAULT_BUTTON_LABELS: Required<ButtonLabels> = {\n tour: 'Tour',\n explore: 'Explore',\n hybrid: 'Hybrid',\n walk: 'Walk',\n orbit: 'Orbit',\n fly: 'Fly',\n next: 'Next',\n previous: 'Prev',\n startExperience: 'Start Experience',\n fullscreen: 'Fullscreen',\n mute: 'Mute',\n unmute: 'Unmute',\n waypoints: 'Waypoints',\n close: 'Close',\n yes: 'Yes',\n cancel: 'Cancel',\n switchScenes: 'Switch scenes?',\n hotspotDefaultTitle: 'Hotspot',\n openExternalLink: 'Open External Link',\n vr: 'VR',\n ar: 'AR',\n exitVr: 'Exit VR',\n exitAr: 'Exit AR',\n loading: 'Loading...',\n loadingScene: 'Loading {name}...',\n helpTitle: 'Controls & Help',\n helpCameraModes: 'Camera Modes:',\n helpTourDesc: 'Follow predefined path',\n helpExploreDesc: 'Free movement',\n helpWalkDesc: 'First-person walking',\n errorWebGLTitle: 'Unable to Initialize 3D Graphics',\n errorWebGLMessage: 'Your browser or device may not support WebGL. Please try a different browser or device.',\n percentageFormat: '{n}%'\n};\n/**\n * Get a button label, falling back to default if not provided\n */\nexport function getButtonLabel(labels: ButtonLabels | undefined, key: keyof ButtonLabels): string {\n return labels?.[key] || DEFAULT_BUTTON_LABELS[key];\n}\n/**\n * Format a percentage label using the configured format\n */\nfunction formatPercentageLabel(labels: ButtonLabels | undefined, value: number): string {\n const format = labels?.percentageFormat || DEFAULT_BUTTON_LABELS.percentageFormat;\n return format.replace('{n}', String(value));\n}\nexport interface UIOptions {\n uiColor?: string;\n showScrollControls?: boolean;\n showModeToggle?: boolean;\n showFullscreenButton?: boolean;\n showHelpButton?: boolean;\n showMuteButton?: boolean;\n showPreloader?: boolean;\n allowedCameraModes?: string[];\n defaultCameraMode?: string;\n customPreloaderLogoUrl?: string;\n /** Custom button labels for internationalization (i18n) */\n buttonLabels?: ButtonLabels;\n /** Whether to hide the watermark (whitelabeling) */\n hideWatermark?: boolean;\n /** Custom watermark text (whitelabeling) */\n watermarkText?: string;\n /** Custom watermark link URL (whitelabeling) */\n watermarkLink?: string;\n /** Scene ID for default watermark link */\n sceneId?: string;\n /** Show dropdown list for quick waypoint navigation */\n showWaypointList?: boolean;\n /** UI layout template: 'minimal' (default), 'standard' (centered), 'pro' (dark) */\n template?: 'standard' | 'minimal' | 'pro';\n /** Enable debug mode (shows FPS counter) */\n debugMode?: boolean;\n}\nexport interface UIElements {\n preloader?: HTMLElement;\n scrollControls?: HTMLElement;\n modeToggle?: HTMLElement;\n fullscreenButton?: HTMLElement;\n helpButton?: HTMLElement;\n helpPanel?: HTMLElement;\n waypointInfo?: HTMLElement;\n progressBar?: HTMLElement;\n progressText?: HTMLElement;\n hotspotPopup?: HTMLElement;\n portalPopup?: HTMLElement;\n joystick?: HTMLElement;\n joystickThumb?: HTMLElement;\n lookZone?: HTMLElement;\n vrButton?: HTMLElement;\n arButton?: HTMLElement;\n watermark?: HTMLElement;\n waypointListContainer?: HTMLElement;\n fpsCounter?: HTMLElement;\n muteButton?: HTMLElement;\n}\n/**\n * Generate CSS styles for the viewer UI.\n * Base styles are always the minimal theme. Standard and pro templates\n * are applied as CSS override blocks appended after the base.\n */\nexport function generateViewerStyles(uiColor: string = '#4CAF50', template: string = 'minimal'): string {\n return `\n /* Container - CRITICAL: must be position relative and contained */\n .storysplat-viewer-container {\n position: relative;\n width: 100%;\n height: 100%;\n overflow: hidden;\n font-family: system-ui, -apple-system, sans-serif;\n box-sizing: border-box;\n }\n\n .storysplat-viewer-container * {\n box-sizing: border-box;\n }\n\n /* Style isolation: reset common elements to prevent parent page styles from leaking in.\n Uses :where() for zero specificity so individual class rules always win. */\n .storysplat-viewer-container :where(button, a) {\n all: unset;\n box-sizing: border-box;\n display: inline-block;\n font-family: inherit;\n font-size: inherit;\n line-height: normal;\n cursor: pointer;\n }\n\n .storysplat-viewer-container button:focus-visible,\n .storysplat-viewer-container a:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 2px;\n }\n\n .storysplat-viewer-container canvas {\n position: absolute;\n top: 0;\n left: 0;\n width: 100% !important;\n height: 100% !important;\n display: block;\n touch-action: none;\n }\n\n /* Preloader - semi-transparent to see loading/reveal effect */\n .storysplat-preloader {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(30, 30, 30, 0.85);\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n z-index: 100000;\n transition: opacity 0.5s ease-out;\n }\n\n .storysplat-preloader.hidden {\n opacity: 0;\n pointer-events: none;\n }\n\n .storysplat-preloader-content {\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 40px;\n gap: 20px;\n }\n\n .storysplat-preloader-media {\n display: flex;\n align-items: center;\n gap: 40px;\n }\n\n .storysplat-preloader-image {\n height: 200px;\n width: auto;\n object-fit: contain;\n }\n\n .storysplat-preloader-image-inverted {\n height: 200px;\n width: auto;\n object-fit: contain;\n filter: invert(1);\n margin-right: -100px;\n }\n\n .storysplat-preloader-lottie {\n height: 200px;\n width: 200px;\n }\n\n @media (max-width: 768px) {\n .storysplat-preloader-image,\n .storysplat-preloader-image-inverted {\n height: 100px;\n }\n .storysplat-preloader-image-inverted {\n margin-right: -50px;\n }\n .storysplat-preloader-lottie {\n height: 100px;\n width: 100px;\n }\n }\n\n .storysplat-preloader-progress {\n width: 200px;\n height: 4px;\n background: rgba(255, 255, 255, 0.1);\n border-radius: 2px;\n margin-top: 20px;\n position: relative;\n }\n\n .storysplat-preloader-bar {\n width: 0%;\n height: 100%;\n background: ${uiColor};\n border-radius: 2px;\n transition: width 0.3s ease-out;\n }\n\n .storysplat-preloader-text {\n position: absolute;\n top: -25px;\n left: 50%;\n transform: translateX(-50%);\n color: white;\n font-size: 14px;\n white-space: nowrap;\n }\n\n /* Minimal Scroll Controls */\n .storysplat-scroll-controls {\n position: absolute;\n bottom: 0;\n left: 50%;\n transform: translateX(-50%);\n width: 150px;\n padding: 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 5px;\n z-index: 1000;\n }\n\n .storysplat-scroll-content {\n width: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 5px;\n }\n\n .storysplat-progress-text {\n font-size: 14px;\n color: white;\n font-variant-numeric: tabular-nums;\n min-width: 40px;\n text-align: center;\n }\n\n .storysplat-progress-container {\n width: 90%;\n max-width: 150px;\n height: 3px;\n background: rgba(255, 255, 255, 0.2);\n border-radius: 2px;\n overflow: hidden;\n }\n\n .storysplat-progress-bar {\n height: 100%;\n background: ${uiColor};\n transition: width 0.3s ease-out;\n width: 0%;\n }\n\n .storysplat-scroll-buttons {\n display: flex;\n justify-content: center;\n gap: 5px;\n z-index: 1000;\n }\n\n /* Minimal Button Style */\n .storysplat-btn {\n background: rgba(0, 0, 0, 0.3);\n border: none;\n color: white;\n padding: 4px 8px;\n font-size: 12px;\n border-radius: 3px;\n cursor: pointer;\n transition: background 0.2s;\n text-align: center;\n }\n\n .storysplat-btn:hover {\n background: rgba(0, 0, 0, 0.5);\n }\n\n .storysplat-btn-play {\n background: rgba(0, 0, 0, 0.3);\n border: none;\n color: white;\n padding: 4px 8px;\n border-radius: 3px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .storysplat-btn-play svg {\n width: 12px;\n height: 12px;\n fill: white;\n }\n\n /* Explore Controls (Orbit/Fly toggle) */\n .storysplat-explore-controls {\n display: none;\n gap: 5px;\n width: 100%;\n justify-content: center;\n }\n\n .storysplat-explore-controls.visible {\n display: flex;\n }\n\n .storysplat-explore-btn {\n flex: 1;\n background: rgba(0, 0, 0, 0.3);\n border: none;\n color: white;\n padding: 6px 12px;\n font-size: 12px;\n border-radius: 3px;\n cursor: pointer;\n transition: background 0.2s;\n text-align: center;\n }\n\n .storysplat-explore-btn.selected {\n background: ${uiColor} !important;\n }\n\n .storysplat-explore-btn:hover {\n background: rgba(0, 0, 0, 0.5);\n }\n\n /* Mode Toggle - Minimal */\n .storysplat-mode-container {\n margin-top: 5px;\n }\n\n .storysplat-mode-toggle {\n display: flex;\n gap: 5px;\n }\n\n .storysplat-mode-btn {\n background: rgba(0, 0, 0, 0.3);\n border: none;\n color: white;\n padding: 3px 6px;\n font-size: 11px;\n border-radius: 2px;\n cursor: pointer;\n transition: all 0.2s ease;\n text-align: center;\n }\n\n .storysplat-mode-btn.selected {\n background: ${uiColor} !important;\n }\n\n .storysplat-mode-btn:hover {\n background: rgba(0, 0, 0, 0.5);\n }\n\n /* Fullscreen Button - Minimal */\n .storysplat-fullscreen-btn {\n position: absolute;\n top: 10px;\n right: 10px;\n background: rgba(0, 0, 0, 0);\n border: none;\n padding: 5px;\n border-radius: 3px;\n cursor: pointer;\n z-index: 1000;\n }\n\n .storysplat-fullscreen-btn svg {\n width: 20px;\n height: 20px;\n fill: white;\n }\n\n /* Mute Button - Minimal */\n .storysplat-mute-btn {\n position: absolute;\n top: 10px;\n right: 40px;\n background: rgba(0, 0, 0, 0);\n border: none;\n padding: 5px;\n border-radius: 3px;\n cursor: pointer;\n z-index: 1000;\n }\n\n .storysplat-mute-btn svg {\n width: 20px;\n height: 20px;\n fill: white;\n }\n\n /* Help Button - Minimal */\n .storysplat-help-btn {\n position: absolute;\n top: 10px;\n left: 10px;\n width: 30px;\n height: 30px;\n border-radius: 50%;\n background: rgba(0, 0, 0, 0.3);\n color: white;\n border: none;\n cursor: pointer;\n font-size: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n transition: all 0.2s ease;\n }\n\n .storysplat-help-btn:hover {\n background: rgba(0, 0, 0, 0.5);\n }\n\n .storysplat-help-panel {\n position: absolute;\n top: 50px;\n left: 10px;\n background: rgba(0, 0, 0, 0.8);\n color: white;\n padding: 15px;\n border-radius: 5px;\n max-width: 280px;\n z-index: 999;\n display: none;\n font-size: 12px;\n }\n\n .storysplat-help-panel.visible {\n display: block;\n }\n\n /* XR (VR/AR) Buttons - Minimal */\n .storysplat-xr-btn {\n position: absolute;\n top: 10px;\n right: 45px;\n background: rgba(0, 0, 0, 0.5);\n border: none;\n padding: 6px 10px;\n border-radius: 4px;\n cursor: pointer;\n z-index: 1000;\n color: white;\n font-size: 11px;\n font-weight: bold;\n text-transform: uppercase;\n transition: all 0.2s ease;\n display: none;\n }\n\n .storysplat-xr-btn.available {\n display: block;\n }\n\n .storysplat-xr-btn.active {\n background: ${uiColor};\n }\n\n .storysplat-xr-btn:hover {\n background: rgba(0, 0, 0, 0.7);\n }\n\n .storysplat-xr-btn.active:hover {\n background: ${uiColor};\n opacity: 0.9;\n }\n\n .storysplat-ar-btn {\n right: 85px;\n }\n\n .storysplat-help-panel h3 {\n margin: 0 0 10px 0;\n font-size: 14px;\n font-weight: 600;\n }\n\n .storysplat-help-panel p {\n margin: 5px 0;\n line-height: 1.4;\n }\n\n /* Waypoint Info - Top Banner Style (matching BabylonJS minimal export) */\n .storysplat-waypoint-info {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n background: rgba(0, 0, 0, 0.5);\n color: white;\n text-align: center;\n z-index: 1000;\n pointer-events: none;\n display: none;\n }\n\n .storysplat-waypoint-info.hasContent {\n display: block;\n padding: 50px 30px 30px 30px;\n }\n\n .storysplat-waypoint-info h2 {\n margin: 0 0 8px 0;\n font-size: 18px;\n font-weight: bold;\n }\n\n .storysplat-waypoint-info p {\n margin: 0;\n font-size: 14px;\n line-height: 1.5;\n opacity: 0.9;\n }\n\n /* Responsive waypoint info */\n @media (max-height: 600px) {\n .storysplat-waypoint-info.hasContent {\n padding: 20px 20px 15px 20px;\n font-size: 13px;\n }\n }\n\n @media (max-height: 500px) {\n .storysplat-waypoint-info.hasContent {\n padding: 10px 15px 10px 15px;\n font-size: 12px;\n }\n }\n\n /* Hotspot Popup - Matching BabylonJS HTML export styles */\n /* Uses position: absolute so popup stays within container when embedded */\n .storysplat-hotspot-popup {\n position: absolute;\n background-color: rgba(0, 0, 0, 0.75);\n color: white;\n padding: 20px;\n border-radius: 10px;\n z-index: 100001;\n box-shadow: 0 0 10px rgba(0,0,0,0.5);\n display: none;\n font-size: 14px;\n flex-direction: column;\n font-family: system-ui, -apple-system, sans-serif;\n /* Default centered positioning */\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n max-width: 80%;\n max-height: 80%;\n overflow: auto;\n }\n\n .storysplat-hotspot-popup.visible {\n display: flex;\n }\n\n /* Fullscreen mode for click activation with image/iframe content */\n .storysplat-hotspot-popup.fullscreen {\n top: 20px !important;\n left: 20px !important;\n right: 20px !important;\n bottom: 20px !important;\n transform: none !important;\n width: auto !important;\n height: auto !important;\n max-width: none !important;\n max-height: none !important;\n margin: 0 !important;\n border-radius: 10px !important;\n display: flex !important;\n align-items: center !important;\n justify-content: center !important;\n flex-direction: column !important;\n padding: 0 !important;\n overflow: hidden !important;\n }\n\n .storysplat-hotspot-popup h2,\n .storysplat-hotspot-popup-title {\n margin: 0 0 10px 0;\n font-size: 18px;\n font-weight: 600;\n padding-right: 30px;\n }\n\n .storysplat-hotspot-popup p {\n margin: 0 0 10px 0;\n line-height: 1.5;\n font-size: 14px;\n white-space: pre-wrap;\n max-width: 500px;\n }\n\n .storysplat-hotspot-popup-content {\n max-width: 500px;\n }\n\n .storysplat-hotspot-popup.fullscreen .storysplat-hotspot-popup-title {\n text-align: center;\n width: 100%;\n padding: 20px 20px 10px 20px;\n padding-right: 20px;\n flex-shrink: 0;\n }\n\n .storysplat-hotspot-popup.fullscreen .storysplat-hotspot-popup-content {\n display: flex;\n flex-direction: column;\n align-items: center;\n text-align: center;\n width: 100%;\n max-width: none;\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n }\n\n .storysplat-hotspot-popup.fullscreen p {\n text-align: center;\n max-width: 80%;\n }\n\n .storysplat-hotspot-popup.fullscreen .storysplat-hotspot-popup-link {\n margin: 4px auto;\n }\n\n .storysplat-hotspot-popup.fullscreen .storysplat-hotspot-popup-close {\n display: block;\n margin: 4px auto 20px auto;\n flex-shrink: 0;\n }\n\n .storysplat-hotspot-popup img {\n max-width: 100%;\n height: auto;\n border-radius: 5px;\n margin-bottom: 10px;\n display: block;\n }\n\n .storysplat-hotspot-popup.fullscreen img {\n width: auto !important;\n height: auto !important;\n max-width: 90% !important;\n max-height: 100% !important;\n object-fit: contain !important;\n border-radius: 8px;\n margin: 10px 0;\n flex-shrink: 1;\n min-height: 0;\n }\n\n .storysplat-hotspot-popup iframe {\n width: 100%;\n max-width: 100%;\n aspect-ratio: 16 / 9;\n height: auto;\n border: none;\n border-radius: 5px;\n margin-bottom: 10px;\n }\n\n .storysplat-hotspot-popup.fullscreen iframe {\n width: 90% !important;\n max-width: 800px !important;\n height: auto !important;\n aspect-ratio: 16 / 9 !important;\n max-height: 100% !important;\n margin: 10px 0;\n border-radius: 8px;\n flex-shrink: 1;\n min-height: 0;\n }\n\n /* Portal confirmation popup */\n .storysplat-portal-popup {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: rgba(0, 0, 0, 0.85);\n color: white;\n padding: 20px;\n border-radius: 10px;\n z-index: 100002;\n display: none;\n flex-direction: column;\n gap: 15px;\n min-width: 240px;\n max-width: 80%;\n text-align: center;\n box-shadow: 0 0 12px rgba(0, 0, 0, 0.6);\n }\n\n .storysplat-portal-popup.visible {\n display: flex;\n }\n\n .storysplat-portal-popup-title {\n font-size: 16px;\n font-weight: 600;\n }\n\n .storysplat-portal-popup-actions {\n display: flex;\n justify-content: center;\n gap: 10px;\n }\n\n .storysplat-portal-popup-btn {\n padding: 8px 14px;\n border: none;\n border-radius: 6px;\n cursor: pointer;\n font-size: 13px;\n font-weight: 500;\n }\n\n .storysplat-portal-popup-confirm {\n background: ${uiColor};\n color: white;\n }\n\n .storysplat-portal-popup-cancel {\n background: rgba(255, 255, 255, 0.15);\n color: white;\n }\n\n /* Video container - matches HTML export sizing */\n .storysplat-hotspot-popup .video-container {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 300px;\n max-width: 100%;\n height: auto;\n margin: 10px 0;\n }\n\n .storysplat-hotspot-popup video {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n border-radius: 5px;\n border: 2px solid rgba(255, 255, 255, 0.3);\n }\n\n /* Fullscreen popup video - constrained so title+description+buttons always fit */\n .storysplat-hotspot-popup.fullscreen .video-container {\n width: 90% !important;\n max-width: 800px !important;\n max-height: 100% !important;\n height: auto !important;\n margin: 10px 0 !important;\n flex-shrink: 1;\n min-height: 0;\n }\n\n .storysplat-hotspot-popup.fullscreen video {\n max-width: 100% !important;\n max-height: 100% !important;\n object-fit: contain !important;\n border-radius: 8px;\n }\n\n /* Close button - matching BabylonJS export (green button at bottom) */\n .storysplat-hotspot-popup-close {\n display: inline-block;\n width: auto;\n padding: 10px 20px;\n background-color: #4CAF50;\n border: none;\n color: white;\n cursor: pointer;\n border-radius: 5px;\n margin: 5px 0 0 0;\n font-size: 14px;\n font-weight: 500;\n font-family: system-ui, -apple-system, sans-serif;\n text-align: center;\n transition: background-color 0.2s;\n flex-shrink: 0;\n }\n\n .storysplat-hotspot-popup-close:hover {\n background-color: #45a049;\n }\n\n /* Error Popup - for outdated/broken scenes */\n .storysplat-error-popup {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: rgba(0, 0, 0, 0.95);\n border-radius: 12px;\n padding: 30px 40px;\n max-width: 450px;\n width: 90%;\n text-align: center;\n z-index: 10001;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);\n border: 1px solid rgba(255, 100, 100, 0.3);\n }\n\n .storysplat-error-popup-icon {\n font-size: 48px;\n margin-bottom: 15px;\n }\n\n .storysplat-error-popup-title {\n color: #ff6b6b;\n font-size: 20px;\n font-weight: 600;\n margin: 0 0 15px 0;\n }\n\n .storysplat-error-popup-message {\n color: #ccc;\n font-size: 14px;\n line-height: 1.6;\n margin: 0 0 20px 0;\n }\n\n .storysplat-error-popup-action {\n display: inline-block;\n padding: 12px 24px;\n background: linear-gradient(135deg, #4CAF50, #45a049);\n color: white;\n text-decoration: none;\n border-radius: 6px;\n font-weight: 500;\n font-size: 14px;\n transition: transform 0.2s, box-shadow 0.2s;\n }\n\n .storysplat-error-popup-action:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 12px rgba(76, 175, 80, 0.4);\n }\n\n .storysplat-hotspot-popup-link {\n display: block;\n padding: 8px 16px;\n background: #007bff;\n color: white;\n text-decoration: none;\n border-radius: 4px;\n text-align: center;\n font-weight: 500;\n margin: 0 0 4px 0;\n cursor: pointer;\n transition: background-color 0.2s;\n }\n\n .storysplat-hotspot-popup-link:hover {\n background: #0056b3;\n }\n\n /* Mobile Responsive */\n @media (max-width: 768px) {\n .storysplat-scroll-controls {\n margin-bottom: 10px;\n }\n\n .storysplat-hotspot-popup.fullscreen {\n top: 10px !important;\n left: 10px !important;\n right: 10px !important;\n bottom: 10px !important;\n }\n\n .storysplat-hotspot-popup.fullscreen img {\n max-width: 95% !important;\n }\n\n .storysplat-hotspot-popup.fullscreen .video-container {\n width: 95% !important;\n max-width: 95% !important;\n }\n\n .storysplat-hotspot-popup.fullscreen iframe {\n width: 95% !important;\n max-width: 95% !important;\n }\n\n .storysplat-hotspot-popup-title {\n font-size: 16px !important;\n padding: 10px 15px !important;\n }\n }\n\n @media (max-width: 540px) {\n .storysplat-scroll-controls {\n margin-bottom: 45px;\n }\n\n .storysplat-hotspot-popup.fullscreen img {\n max-width: 98% !important;\n }\n\n .storysplat-hotspot-popup.fullscreen .video-container {\n width: 98% !important;\n max-width: 98% !important;\n }\n\n .storysplat-hotspot-popup.fullscreen iframe {\n width: 98% !important;\n max-width: 98% !important;\n }\n }\n\n /* Virtual Joystick Overlay - visual indicator only, touches pass through to canvas */\n .storysplat-joystick-container {\n position: absolute;\n bottom: 80px;\n left: 40px;\n width: 120px;\n height: 120px;\n z-index: 2000;\n pointer-events: none;\n touch-action: none;\n display: none;\n }\n\n .storysplat-joystick-container.visible {\n display: block;\n }\n\n .storysplat-joystick-base {\n position: absolute;\n top: 0;\n left: 0;\n width: 120px;\n height: 120px;\n background: rgba(255, 255, 255, 0.2);\n border: 3px solid rgba(255, 255, 255, 0.4);\n border-radius: 50%;\n pointer-events: none;\n }\n\n .storysplat-joystick-thumb {\n position: absolute;\n top: 50%;\n left: 50%;\n width: 50px;\n height: 50px;\n margin-left: -25px;\n margin-top: -25px;\n background: rgba(255, 255, 255, 0.6);\n border: 3px solid rgba(255, 255, 255, 0.8);\n border-radius: 50%;\n pointer-events: none;\n will-change: transform;\n }\n\n .storysplat-joystick-thumb.active {\n background: rgba(255, 255, 255, 0.8);\n border-color: white;\n }\n\n /* Look Zone Indicator (right side) */\n .storysplat-look-zone {\n position: absolute;\n bottom: 80px;\n right: 40px;\n width: 100px;\n height: 100px;\n z-index: 1000;\n pointer-events: none;\n display: none;\n }\n\n .storysplat-look-zone.visible {\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .storysplat-look-zone-icon {\n width: 60px;\n height: 60px;\n background: rgba(255, 255, 255, 0.15);\n border: 2px solid rgba(255, 255, 255, 0.25);\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .storysplat-look-zone-icon svg {\n width: 30px;\n height: 30px;\n fill: rgba(255, 255, 255, 0.5);\n }\n\n /* Look Zone Active State */\n .storysplat-look-zone.active .storysplat-look-zone-icon {\n background: rgba(255, 255, 255, 0.25);\n border-color: rgba(255, 255, 255, 0.5);\n }\n\n .storysplat-look-zone.active .storysplat-look-zone-icon svg {\n fill: rgba(255, 255, 255, 0.8);\n }\n\n /* Lazy Load Container */\n .storysplat-lazy-load-container {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n justify-content: center;\n align-items: center;\n background: #1a1a1a;\n z-index: 10001;\n }\n\n .storysplat-lazy-load-thumbnail {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n opacity: 0.7;\n }\n\n .storysplat-lazy-load-thumbnail-video {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n opacity: 0.7;\n }\n\n .storysplat-lazy-load-overlay {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: linear-gradient(\n to bottom,\n rgba(0, 0, 0, 0.3) 0%,\n rgba(0, 0, 0, 0.5) 50%,\n rgba(0, 0, 0, 0.7) 100%\n );\n }\n\n .storysplat-lazy-load-content {\n position: relative;\n z-index: 1;\n text-align: center;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n }\n\n .storysplat-lazy-load-start-btn {\n padding: 16px 32px;\n background: ${uiColor};\n border: none;\n border-radius: 50px;\n color: white;\n font-size: 18px;\n font-weight: 600;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 12px;\n transition: all 0.3s ease;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n }\n\n .storysplat-lazy-load-start-btn:hover {\n transform: scale(1.05);\n box-shadow: 0 6px 30px rgba(0, 0, 0, 0.4);\n }\n\n .storysplat-lazy-load-start-btn:active {\n transform: scale(0.98);\n }\n\n .storysplat-lazy-load-start-btn svg {\n width: 24px;\n height: 24px;\n fill: white;\n }\n\n @media (max-width: 768px) {\n .storysplat-lazy-load-start-btn {\n padding: 12px 24px;\n font-size: 16px;\n }\n }\n\n /* Waypoint List Dropdown - Glassmorphic style */\n .storysplat-waypoint-list-container {\n position: absolute;\n top: 10px;\n right: 45px;\n z-index: 1000;\n }\n\n .storysplat-waypoint-list-toggle {\n padding: 8px 16px;\n background: rgba(0, 0, 0, 0.5);\n border: none;\n border-radius: 8px;\n color: white;\n cursor: pointer;\n font-size: 14px;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n transition: background-color 0.3s ease;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .storysplat-waypoint-list-toggle:hover {\n background: rgba(0, 0, 0, 0.7);\n }\n\n .storysplat-waypoint-list-toggle svg {\n width: 16px;\n height: 16px;\n fill: white;\n transition: transform 0.3s ease;\n }\n\n .storysplat-waypoint-list-toggle.open svg {\n transform: rotate(180deg);\n }\n\n .storysplat-waypoint-list-dropdown {\n position: absolute;\n top: 100%;\n right: 0;\n margin-top: 8px;\n min-width: 200px;\n max-height: 300px;\n overflow-y: auto;\n background: rgba(0, 0, 0, 0.8);\n border-radius: 8px;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n display: none;\n flex-direction: column;\n }\n\n .storysplat-waypoint-list-dropdown.open {\n display: flex;\n }\n\n .storysplat-waypoint-item {\n padding: 12px 16px;\n color: white;\n cursor: pointer;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n transition: background-color 0.2s ease;\n font-size: 14px;\n }\n\n .storysplat-waypoint-item:last-child {\n border-bottom: none;\n }\n\n .storysplat-waypoint-item:hover {\n background: rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-waypoint-item.active {\n background: rgba(76, 175, 80, 0.3);\n }\n\n /* Adjust position when fullscreen button is present */\n .storysplat-waypoint-list-container.with-fullscreen {\n right: 45px;\n }\n\n .storysplat-waypoint-list-container.no-fullscreen {\n right: 10px;\n }\n\n @media (max-width: 768px) {\n .storysplat-waypoint-list-container {\n right: 40px;\n }\n\n .storysplat-waypoint-list-toggle {\n padding: 6px 12px;\n font-size: 12px;\n }\n\n .storysplat-waypoint-list-dropdown {\n min-width: 160px;\n max-height: 250px;\n }\n\n .storysplat-waypoint-item {\n padding: 10px 12px;\n font-size: 12px;\n }\n }\n\n /* Watermark - matches BabylonJS HTML export */\n .storysplat-watermark {\n position: absolute;\n bottom: 10px;\n right: 10px;\n background-color: rgba(0, 0, 0, 0.5);\n color: white;\n padding: 5px 10px;\n border-radius: 5px;\n font-size: 12px;\n z-index: 1000;\n pointer-events: auto; /* Allow pointer events for the entire watermark so links work */\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;\n cursor: default;\n }\n\n .storysplat-watermark a {\n color: ${uiColor};\n text-decoration: none;\n cursor: pointer;\n }\n\n .storysplat-watermark a:hover {\n text-decoration: underline;\n }\n\n .storysplat-fps-counter {\n position: absolute;\n top: 8px;\n left: 8px;\n background: rgba(0, 0, 0, 0.6);\n color: #0f0;\n padding: 4px 8px;\n border-radius: 4px;\n font-size: 12px;\n font-family: monospace;\n z-index: 1000;\n pointer-events: none;\n line-height: 1;\n }\n ` + generateTemplateOverrides(uiColor, template);\n}\n\n/**\n * Generate template-specific CSS overrides.\n * These are appended after the minimal base styles with higher cascade priority.\n */\nfunction generateTemplateOverrides(uiColor: string, template: string): string {\n if (template === 'standard') {\n return `\n /* ===== STANDARD (Centered) Template Overrides ===== */\n .storysplat-scroll-controls {\n width: auto;\n max-width: 350px;\n padding: 15px 20px;\n background: rgba(0, 0, 0, 0.7);\n border-radius: 10px;\n bottom: 20px;\n gap: 8px;\n }\n\n .storysplat-scroll-content {\n gap: 8px;\n }\n\n .storysplat-progress-container {\n max-width: 300px;\n height: 10px;\n border-radius: 5px;\n }\n\n .storysplat-progress-bar {\n border-radius: 5px;\n }\n\n .storysplat-progress-text {\n font-size: 16px;\n }\n\n .storysplat-scroll-buttons {\n gap: 8px;\n }\n\n .storysplat-btn {\n background: ${uiColor};\n padding: 10px 20px;\n font-size: 16px;\n border-radius: 5px;\n }\n\n .storysplat-btn:hover {\n opacity: 0.85;\n background: ${uiColor};\n }\n\n .storysplat-btn-play {\n padding: 10px 12px;\n background: ${uiColor};\n border-radius: 5px;\n }\n\n .storysplat-btn-play svg {\n width: 16px;\n height: 16px;\n }\n\n .storysplat-btn-play:hover {\n opacity: 0.85;\n background: ${uiColor};\n }\n\n .storysplat-mode-container {\n margin-top: 8px;\n }\n\n .storysplat-mode-toggle {\n gap: 8px;\n }\n\n .storysplat-mode-btn {\n padding: 6px 14px;\n font-size: 14px;\n border-radius: 4px;\n border: 1px solid rgba(255, 255, 255, 0.2);\n }\n\n .storysplat-explore-btn {\n padding: 8px 16px;\n font-size: 14px;\n border-radius: 4px;\n }\n\n @media (max-width: 768px) {\n .storysplat-scroll-controls {\n max-width: 280px;\n padding: 12px 15px;\n }\n\n .storysplat-btn {\n padding: 8px 16px;\n font-size: 14px;\n }\n\n .storysplat-progress-container {\n height: 8px;\n }\n\n .storysplat-progress-text {\n font-size: 14px;\n }\n }\n `;\n }\n\n if (template === 'pro') {\n return `\n /* ===== PRO (Dark) Template Overrides ===== */\n .storysplat-scroll-controls {\n width: 80%;\n max-width: 600px;\n padding: 12px 20px;\n background: rgba(0, 0, 0, 0.5);\n border-radius: 8px;\n bottom: 20px;\n left: 50%;\n transform: translateX(-50%);\n gap: 6px;\n }\n\n .storysplat-scroll-content {\n gap: 6px;\n }\n\n .storysplat-progress-container {\n max-width: 100%;\n height: 2px;\n border-radius: 1px;\n }\n\n .storysplat-progress-bar {\n border-radius: 1px;\n }\n\n .storysplat-progress-text {\n font-size: 13px;\n opacity: 0.8;\n }\n\n .storysplat-scroll-buttons {\n gap: 6px;\n }\n\n .storysplat-btn {\n background: rgba(0, 0, 0, 0.5);\n padding: 8px 16px;\n font-size: 14px;\n border-radius: 4px;\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-btn:hover {\n background: rgba(0, 0, 0, 0.7);\n border-color: rgba(255, 255, 255, 0.2);\n }\n\n .storysplat-btn-play {\n padding: 8px 10px;\n background: rgba(0, 0, 0, 0.5);\n border-radius: 4px;\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-btn-play svg {\n width: 14px;\n height: 14px;\n }\n\n .storysplat-btn-play:hover {\n background: rgba(0, 0, 0, 0.7);\n border-color: rgba(255, 255, 255, 0.2);\n }\n\n /* Pro: mode toggle on the left side, vertical stack */\n .storysplat-mode-container {\n position: absolute;\n bottom: 20px;\n left: 20px;\n margin-top: 0;\n z-index: 1000;\n }\n\n .storysplat-mode-toggle {\n flex-direction: column;\n gap: 4px;\n }\n\n .storysplat-mode-btn {\n padding: 6px 12px;\n font-size: 13px;\n border-radius: 4px;\n background: rgba(0, 0, 0, 0.5);\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-mode-btn:hover {\n background: rgba(0, 0, 0, 0.7);\n border-color: rgba(255, 255, 255, 0.2);\n }\n\n .storysplat-explore-controls {\n flex-direction: column;\n }\n\n .storysplat-explore-btn {\n padding: 6px 14px;\n font-size: 13px;\n border-radius: 4px;\n background: rgba(0, 0, 0, 0.5);\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .storysplat-explore-btn:hover {\n background: rgba(0, 0, 0, 0.7);\n }\n\n @media (max-width: 768px) {\n .storysplat-scroll-controls {\n width: 70%;\n max-width: none;\n padding: 10px 15px;\n bottom: 10px;\n }\n\n .storysplat-mode-container {\n bottom: 10px;\n left: 10px;\n }\n\n .storysplat-btn {\n padding: 6px 12px;\n font-size: 12px;\n }\n }\n `;\n }\n\n // 'minimal' (default) — no overrides needed\n return '';\n}\n/**\n * Inject CSS styles into the document\n */\nexport function injectStyles(uiColor: string = '#4CAF50', template: string = 'minimal'): HTMLStyleElement {\n const existingStyle = document.getElementById('storysplat-viewer-styles');\n if (existingStyle) {\n existingStyle.remove();\n }\n const style = document.createElement('style');\n style.id = 'storysplat-viewer-styles';\n style.textContent = generateViewerStyles(uiColor, template);\n document.head.appendChild(style);\n return style;\n}\n// StorySplat default assets\nconst STORYSPLAT_LOGO_URL = 'https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fimages%2FStorySplat.webp?alt=media&token=953e8ab3-1865-4ac1-a98d-b548b7066bda';\nconst STORYSPLAT_LOTTIE_URL = 'https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Flotties%2FstorySplatLottie.json?alt=media&token=d7edc19d-9cb8-4c6e-a94c-cba1d2b65d5e';\n/**\n * Inject Lottie player script if not already loaded\n */\nfunction ensureLottiePlayer(): Promise<void> {\n return new Promise((resolve) => {\n // Check if already loaded\n if (customElements.get('lottie-player')) {\n resolve();\n return;\n }\n // Check if script is already being loaded\n const existingScript = document.querySelector('script[src*=\"lottie-player\"]');\n if (existingScript) {\n existingScript.addEventListener('load', () => resolve());\n return;\n }\n // Load the script\n const script = document.createElement('script');\n script.src = 'https://unpkg.com/@lottiefiles/lottie-player@latest/dist/lottie-player.js';\n script.onload = () => resolve();\n document.head.appendChild(script);\n });\n}\n/**\n * Create preloader element\n */\nexport function createPreloader(container: HTMLElement, customLogoUrl?: string, buttonLabels?: ButtonLabels): HTMLElement {\n const preloader = document.createElement('div');\n preloader.className = 'storysplat-preloader';\n const useCustomLogo = !!customLogoUrl;\n preloader.innerHTML = `\n <div class=\"storysplat-preloader-content\">\n <div class=\"storysplat-preloader-media\">\n ${useCustomLogo\n ? `<img class=\"storysplat-preloader-image\" src=\"${customLogoUrl}\" alt=\"Custom Logo\" />`\n : `<img class=\"storysplat-preloader-image-inverted\" src=\"${STORYSPLAT_LOGO_URL}\" alt=\"StorySplat Logo\" />`}\n ${!useCustomLogo\n ? `<lottie-player class=\"storysplat-preloader-lottie\"\n src=\"${STORYSPLAT_LOTTIE_URL}\"\n background=\"transparent\"\n speed=\"1\"\n loop\n autoplay>\n </lottie-player>`\n : ''}\n </div>\n <div class=\"storysplat-preloader-progress\">\n <div class=\"storysplat-preloader-text\">${getButtonLabel(buttonLabels, 'loading')} 0%</div>\n <div class=\"storysplat-preloader-bar\"></div>\n </div>\n </div>\n `;\n container.appendChild(preloader);\n // Load Lottie player script if using default logo\n if (!useCustomLogo) {\n ensureLottiePlayer();\n }\n return preloader;\n}\n/**\n * Update preloader progress\n */\nexport function updatePreloaderProgress(preloader: HTMLElement, progress: number, text?: string, loadingLabel?: string): void {\n const bar = preloader.querySelector('.storysplat-preloader-bar') as HTMLElement;\n const textEl = preloader.querySelector('.storysplat-preloader-text') as HTMLElement;\n const percent = Math.max(0, Math.min(100, progress * 100));\n if (bar)\n bar.style.width = `${percent}%`;\n if (textEl)\n textEl.textContent = text || `${loadingLabel || DEFAULT_BUTTON_LABELS.loading} ${Math.round(percent)}%`;\n}\n/**\n * Hide preloader\n */\nexport function hidePreloader(preloader: HTMLElement): void {\n preloader.classList.add('hidden');\n setTimeout(() => preloader.remove(), 500);\n}\n/**\n * Show error popup for load failures (e.g., outdated scenes that need re-export)\n */\nexport function showErrorPopup(container: HTMLElement, errorMessage: string): void {\n // Remove any existing error popup\n container.querySelector('.storysplat-error-popup')?.remove();\n // Also hide the preloader if visible\n const preloader = container.querySelector('.storysplat-preloader') as HTMLElement;\n if (preloader) {\n hidePreloader(preloader);\n }\n // Determine if this is an outdated scene error\n const isOutdatedScene = errorMessage.includes('Failed to load splat from any URL');\n const popup = document.createElement('div');\n popup.className = 'storysplat-error-popup';\n if (isOutdatedScene) {\n popup.innerHTML = `\n <div class=\"storysplat-error-popup-icon\">⚠️</div>\n <h3 class=\"storysplat-error-popup-title\">Scene Needs Update</h3>\n <p class=\"storysplat-error-popup-message\">\n This scene was created with an older version of StorySplat and needs to be re-exported to work with the latest viewer.\n <br><br>\n Please ask the scene creator to re-export it from the StorySplat editor.\n </p>\n <a href=\"https://storysplat.com\" target=\"_blank\" class=\"storysplat-error-popup-action\">\n Visit StorySplat\n </a>\n `;\n }\n else {\n popup.innerHTML = `\n <div class=\"storysplat-error-popup-icon\">❌</div>\n <h3 class=\"storysplat-error-popup-title\">Failed to Load Scene</h3>\n <p class=\"storysplat-error-popup-message\">\n ${errorMessage || 'An error occurred while loading this scene. Please try refreshing the page.'}\n </p>\n `;\n }\n container.appendChild(popup);\n}\n/**\n * Create UI elements for the viewer\n */\nexport function createUIElements(container: HTMLElement, config: ViewerConfig, options: UIOptions = {}): UIElements {\n const { uiColor = '#4CAF50', showScrollControls = true, showModeToggle = true, showFullscreenButton = true, showHelpButton = false, // Minimal: hide by default\n showMuteButton = false, showPreloader = true, allowedCameraModes = ['tour', 'explore'], defaultCameraMode = 'tour', customPreloaderLogoUrl, buttonLabels, hideWatermark = false, watermarkText, watermarkLink, sceneId, showWaypointList = true, template = 'minimal' } = options;\n // Get localized labels\n const labels = {\n tour: getButtonLabel(buttonLabels, 'tour'),\n explore: getButtonLabel(buttonLabels, 'explore'),\n walk: getButtonLabel(buttonLabels, 'walk'),\n orbit: getButtonLabel(buttonLabels, 'orbit'),\n fly: getButtonLabel(buttonLabels, 'fly'),\n previous: getButtonLabel(buttonLabels, 'previous'),\n next: getButtonLabel(buttonLabels, 'next'),\n fullscreen: getButtonLabel(buttonLabels, 'fullscreen'),\n waypoints: getButtonLabel(buttonLabels, 'waypoints'),\n close: getButtonLabel(buttonLabels, 'close'),\n yes: getButtonLabel(buttonLabels, 'yes'),\n cancel: getButtonLabel(buttonLabels, 'cancel'),\n vr: getButtonLabel(buttonLabels, 'vr'),\n ar: getButtonLabel(buttonLabels, 'ar'),\n loading: getButtonLabel(buttonLabels, 'loading'),\n helpTitle: getButtonLabel(buttonLabels, 'helpTitle'),\n helpCameraModes: getButtonLabel(buttonLabels, 'helpCameraModes'),\n helpTourDesc: getButtonLabel(buttonLabels, 'helpTourDesc'),\n helpExploreDesc: getButtonLabel(buttonLabels, 'helpExploreDesc'),\n helpWalkDesc: getButtonLabel(buttonLabels, 'helpWalkDesc'),\n hotspotDefaultTitle: getButtonLabel(buttonLabels, 'hotspotDefaultTitle'),\n openExternalLink: getButtonLabel(buttonLabels, 'openExternalLink'),\n mute: getButtonLabel(buttonLabels, 'mute'),\n unmute: getButtonLabel(buttonLabels, 'unmute'),\n };\n const elements: UIElements = {};\n // Clean up any existing UI elements from previous renders\n container.querySelectorAll('.storysplat-preloader, .storysplat-scroll-controls, .storysplat-waypoint-info, .storysplat-fullscreen-btn, .storysplat-mute-btn, .storysplat-help-btn, .storysplat-help-panel, .storysplat-watermark, .storysplat-waypoint-list-container, .storysplat-hotspot-popup, .storysplat-portal-popup').forEach(el => el.remove());\n // Inject styles (template-aware)\n injectStyles(uiColor, template);\n // Add container class\n container.classList.add('storysplat-viewer-container');\n // Create Preloader\n if (showPreloader) {\n elements.preloader = createPreloader(container, customPreloaderLogoUrl, buttonLabels);\n }\n // Create Waypoint Info Panel (top banner style)\n const waypointInfo = document.createElement('div');\n waypointInfo.className = 'storysplat-waypoint-info';\n waypointInfo.innerHTML = `\n <h2 class=\"storysplat-waypoint-title\"></h2>\n <p class=\"storysplat-waypoint-description\"></p>\n `;\n container.appendChild(waypointInfo);\n elements.waypointInfo = waypointInfo;\n // Create Scroll Controls\n if (showScrollControls && config.waypoints && config.waypoints.length > 0) {\n const scrollControls = document.createElement('div');\n scrollControls.className = 'storysplat-scroll-controls';\n scrollControls.innerHTML = `\n <div class=\"storysplat-scroll-content\">\n <div class=\"storysplat-progress-text\">0%</div>\n <div class=\"storysplat-progress-container\">\n <div class=\"storysplat-progress-bar\"></div>\n </div>\n <div class=\"storysplat-scroll-buttons\">\n <button class=\"storysplat-btn storysplat-btn-prev\">${labels.previous}</button>\n <button class=\"storysplat-btn storysplat-btn-play\">\n <svg viewBox=\"0 0 24 24\"><path d=\"M8 5v14l11-7z\"/></svg>\n </button>\n <button class=\"storysplat-btn storysplat-btn-next\">${labels.next}</button>\n </div>\n <div class=\"storysplat-explore-controls\">\n <button class=\"storysplat-explore-btn\" data-explore-mode=\"orbit\">${labels.orbit}</button>\n <button class=\"storysplat-explore-btn\" data-explore-mode=\"fly\">${labels.fly}</button>\n </div>\n ${showModeToggle && allowedCameraModes.length > 1 ? `\n <div class=\"storysplat-mode-container\">\n <div class=\"storysplat-mode-toggle\">\n ${allowedCameraModes.includes('tour') ? `<button class=\"storysplat-mode-btn ${defaultCameraMode === 'tour' ? 'selected' : ''}\" data-mode=\"tour\">${labels.tour}</button>` : ''}\n ${allowedCameraModes.includes('explore') ? `<button class=\"storysplat-mode-btn ${defaultCameraMode === 'explore' ? 'selected' : ''}\" data-mode=\"explore\">${labels.explore}</button>` : ''}\n ${allowedCameraModes.includes('walk') ? `<button class=\"storysplat-mode-btn ${defaultCameraMode === 'walk' ? 'selected' : ''}\" data-mode=\"walk\">${labels.walk}</button>` : ''}\n </div>\n </div>\n ` : ''}\n </div>\n `;\n container.appendChild(scrollControls);\n elements.scrollControls = scrollControls;\n elements.progressBar = scrollControls.querySelector('.storysplat-progress-bar') as HTMLElement;\n elements.progressText = scrollControls.querySelector('.storysplat-progress-text') as HTMLElement;\n // Pro template: move mode container out of scroll controls to viewer root\n // so it can be positioned independently on the left side via CSS\n if (template === 'pro') {\n const modeContainer = scrollControls.querySelector('.storysplat-mode-container');\n if (modeContainer) {\n container.appendChild(modeContainer);\n }\n }\n }\n // Create Fullscreen Button\n if (showFullscreenButton) {\n const fullscreenBtn = document.createElement('button');\n fullscreenBtn.className = 'storysplat-fullscreen-btn';\n fullscreenBtn.setAttribute('aria-label', labels.fullscreen);\n fullscreenBtn.innerHTML = `\n <svg class=\"storysplat-expand-icon\" viewBox=\"0 0 24 24\">\n <path d=\"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z\"/>\n </svg>\n <svg class=\"storysplat-compress-icon\" viewBox=\"0 0 24 24\" style=\"display: none;\">\n <path d=\"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z\"/>\n </svg>\n `;\n container.appendChild(fullscreenBtn);\n elements.fullscreenButton = fullscreenBtn;\n }\n // Create Mute Button (shown when scene has audio)\n if (showMuteButton) {\n const muteBtn = document.createElement('button');\n muteBtn.className = 'storysplat-mute-btn';\n muteBtn.setAttribute('aria-label', labels.mute);\n // Volume on icon\n const unmutedIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n unmutedIcon.classList.add('storysplat-unmuted-icon');\n unmutedIcon.setAttribute('viewBox', '0 0 24 24');\n const unmutedPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n unmutedPath.setAttribute('d', 'M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z');\n unmutedIcon.appendChild(unmutedPath);\n muteBtn.appendChild(unmutedIcon);\n // Volume off icon (hidden by default)\n const mutedIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n mutedIcon.classList.add('storysplat-muted-icon');\n mutedIcon.setAttribute('viewBox', '0 0 24 24');\n mutedIcon.style.display = 'none';\n const mutedPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n mutedPath.setAttribute('d', 'M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z');\n mutedIcon.appendChild(mutedPath);\n muteBtn.appendChild(mutedIcon);\n container.appendChild(muteBtn);\n elements.muteButton = muteBtn;\n }\n // Create Waypoint List Dropdown (for quick navigation to waypoints)\n if (showWaypointList && config.waypoints && config.waypoints.length > 0) {\n const waypointListContainer = document.createElement('div');\n waypointListContainer.className = `storysplat-waypoint-list-container ${showFullscreenButton ? 'with-fullscreen' : 'no-fullscreen'}`;\n // Build waypoint items HTML\n const waypointItemsHtml = config.waypoints.map((wp, index) => {\n const waypointName = wp.name || `Waypoint ${index + 1}`;\n return `<div class=\"storysplat-waypoint-item\" data-waypoint-index=\"${index}\">${waypointName}</div>`;\n }).join('');\n waypointListContainer.innerHTML = `\n <button class=\"storysplat-waypoint-list-toggle\" aria-label=\"${labels.waypoints}\">\n ${labels.waypoints}\n <svg viewBox=\"0 0 24 24\">\n <path d=\"M7 10l5 5 5-5z\"/>\n </svg>\n </button>\n <div class=\"storysplat-waypoint-list-dropdown\">\n ${waypointItemsHtml}\n </div>\n `;\n container.appendChild(waypointListContainer);\n elements.waypointListContainer = waypointListContainer;\n // Setup toggle behavior\n const toggleBtn = waypointListContainer.querySelector('.storysplat-waypoint-list-toggle');\n const dropdown = waypointListContainer.querySelector('.storysplat-waypoint-list-dropdown');\n toggleBtn?.addEventListener('click', (e) => {\n e.stopPropagation();\n toggleBtn.classList.toggle('open');\n dropdown?.classList.toggle('open');\n });\n // Close dropdown when clicking outside\n document.addEventListener('click', (e) => {\n if (!waypointListContainer.contains(e.target as Node)) {\n toggleBtn?.classList.remove('open');\n dropdown?.classList.remove('open');\n }\n });\n }\n // Create VR Button (hidden by default, shown when VR is available)\n const vrBtn = document.createElement('button');\n vrBtn.className = 'storysplat-xr-btn storysplat-vr-btn';\n vrBtn.setAttribute('aria-label', labels.vr);\n vrBtn.textContent = labels.vr;\n container.appendChild(vrBtn);\n elements.vrButton = vrBtn;\n // Create AR Button (hidden by default, shown when AR is available)\n const arBtn = document.createElement('button');\n arBtn.className = 'storysplat-xr-btn storysplat-ar-btn';\n arBtn.setAttribute('aria-label', labels.ar);\n arBtn.textContent = labels.ar;\n container.appendChild(arBtn);\n elements.arButton = arBtn;\n // Create Help Button and Panel (optional in minimal)\n if (showHelpButton) {\n const helpBtn = document.createElement('button');\n helpBtn.className = 'storysplat-help-btn';\n helpBtn.setAttribute('title', labels.helpTitle);\n helpBtn.textContent = '?';\n container.appendChild(helpBtn);\n elements.helpButton = helpBtn;\n const helpPanel = document.createElement('div');\n helpPanel.className = 'storysplat-help-panel';\n helpPanel.innerHTML = `\n <h3>${labels.helpTitle}</h3>\n <p><strong>${labels.helpCameraModes}</strong></p>\n <p>• ${labels.tour} - ${labels.helpTourDesc}</p>\n <p>• ${labels.explore} - ${labels.helpExploreDesc}</p>\n <p>• ${labels.walk} - ${labels.helpWalkDesc}</p>\n <br/>\n <p><strong>${labels.tour} Mode:</strong></p>\n <p>• Scroll - Move along path</p>\n <p>• Drag - Look around</p>\n <br/>\n <p><strong>${labels.explore} Mode:</strong></p>\n <p>• LMB Drag - Orbit camera</p>\n <p>• RMB Drag - Fly/look</p>\n <p>• WASD/QE - Move camera</p>\n <p>• Shift - Move fast</p>\n <p>• Scroll/Pinch - Zoom</p>\n <p>• Double-click - Focus</p>\n <br/>\n <p><strong>${labels.walk} Mode:</strong></p>\n <p>• Click to lock mouse</p>\n <p>• WASD/Arrows - Move</p>\n <p>• Mouse - Look around</p>\n <p>• Shift - Sprint</p>\n <p>• Space - Jump</p>\n `;\n container.appendChild(helpPanel);\n elements.helpPanel = helpPanel;\n }\n // Create Hotspot Popup (always created, shown when needed)\n // Note: No overlay - matching BabylonJS export behavior\n const hotspotPopup = document.createElement('div');\n hotspotPopup.className = 'storysplat-hotspot-popup';\n hotspotPopup.id = 'hotspotContent'; // Match BabylonJS export ID\n hotspotPopup.innerHTML = `\n <h2 class=\"storysplat-hotspot-popup-title\"></h2>\n <div class=\"storysplat-hotspot-popup-content\"></div>\n <button class=\"storysplat-hotspot-popup-close\">${labels.close}</button>\n `;\n container.appendChild(hotspotPopup);\n elements.hotspotPopup = hotspotPopup;\n // Close popup on close button click\n const closeBtn = hotspotPopup.querySelector('.storysplat-hotspot-popup-close');\n closeBtn?.addEventListener('click', () => {\n hotspotPopup.classList.remove('visible', 'fullscreen');\n });\n // Create Portal Confirmation Popup\n const portalPopup = document.createElement('div');\n portalPopup.className = 'storysplat-portal-popup';\n portalPopup.innerHTML = `\n <div class=\"storysplat-portal-popup-title\"></div>\n <div class=\"storysplat-portal-popup-actions\">\n <button class=\"storysplat-portal-popup-btn storysplat-portal-popup-confirm\">${labels.yes}</button>\n <button class=\"storysplat-portal-popup-btn storysplat-portal-popup-cancel\">${labels.cancel}</button>\n </div>\n `;\n container.appendChild(portalPopup);\n elements.portalPopup = portalPopup;\n // Create Virtual Joystick (for mobile explore mode)\n const joystick = document.createElement('div');\n joystick.className = 'storysplat-joystick-container';\n joystick.innerHTML = `\n <div class=\"storysplat-joystick-base\"></div>\n <div class=\"storysplat-joystick-thumb\"></div>\n `;\n container.appendChild(joystick);\n elements.joystick = joystick;\n elements.joystickThumb = joystick.querySelector('.storysplat-joystick-thumb') as HTMLElement;\n // Create Look Zone indicator (right side)\n const lookZone = document.createElement('div');\n lookZone.className = 'storysplat-look-zone';\n lookZone.innerHTML = `\n <div class=\"storysplat-look-zone-icon\">\n <svg viewBox=\"0 0 24 24\">\n <path d=\"M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z\"/>\n </svg>\n </div>\n `;\n container.appendChild(lookZone);\n elements.lookZone = lookZone;\n // Create Watermark (unless hidden for whitelabeling)\n if (!hideWatermark) {\n const watermark = document.createElement('div');\n watermark.className = 'storysplat-watermark';\n // Build watermark content - matches BabylonJS HTML export behavior\n const defaultWatermarkLink = sceneId\n ? `https://storysplat.com?ref=${sceneId}`\n : 'https://storysplat.com';\n const finalLink = watermarkLink || defaultWatermarkLink;\n if (watermarkText) {\n // Custom text provided (whitelabeling)\n watermark.innerHTML = `<a href=\"${finalLink}\" target=\"_blank\">${watermarkText}</a>`;\n }\n else {\n // Default StorySplat watermark\n watermark.innerHTML = `Created with <a href=\"${finalLink}\" target=\"_blank\">StorySplat</a>`;\n }\n container.appendChild(watermark);\n elements.watermark = watermark;\n }\n // Create FPS counter (debug mode)\n if (options.debugMode) {\n const fpsCounter = document.createElement('div');\n fpsCounter.className = 'storysplat-fps-counter';\n fpsCounter.textContent = '-- FPS';\n container.appendChild(fpsCounter);\n elements.fpsCounter = fpsCounter;\n }\n return elements;\n}\n/**\n * Update the FPS counter display\n */\nexport function updateFpsCounter(elements: UIElements, fps: number): void {\n if (elements.fpsCounter) {\n elements.fpsCounter.textContent = `${Math.round(fps)} FPS`;\n }\n}\n/**\n * Connect UI elements to viewer controls\n */\nexport function connectUIToViewer(elements: UIElements, viewer: ViewerUIController, defaultCameraMode: string = 'tour', buttonLabels?: ButtonLabels): void {\n // Prev/Next buttons\n const prevBtn = elements.scrollControls?.querySelector('.storysplat-btn-prev');\n const nextBtn = elements.scrollControls?.querySelector('.storysplat-btn-next');\n const playBtn = elements.scrollControls?.querySelector('.storysplat-btn-play');\n if (prevBtn) {\n prevBtn.addEventListener('click', () => viewer.prevWaypoint());\n }\n if (nextBtn) {\n nextBtn.addEventListener('click', () => viewer.nextWaypoint());\n }\n if (playBtn) {\n // Update button icon based on state\n const updatePlayButtonIcon = (isPlaying: boolean) => {\n if (isPlaying) {\n playBtn.innerHTML = '<svg viewBox=\"0 0 24 24\"><path d=\"M6 4h4v16H6zm8 0h4v16h-4z\"/></svg>'; // Pause icon\n }\n else {\n playBtn.innerHTML = '<svg viewBox=\"0 0 24 24\"><path d=\"M8 5v14l11-7z\"/></svg>'; // Play icon\n }\n };\n playBtn.addEventListener('click', () => {\n if (viewer.isPlaying()) {\n viewer.pause();\n }\n else {\n viewer.play();\n }\n // Icon will be updated by event listeners below\n });\n // Listen for playback events to update button state (handles autoplay and programmatic play/pause)\n viewer.on('playbackStart', () => updatePlayButtonIcon(true));\n viewer.on('playbackStop', () => updatePlayButtonIcon(false));\n // Set initial state based on current playing status\n updatePlayButtonIcon(viewer.isPlaying());\n }\n // Help button\n if (elements.helpButton && elements.helpPanel) {\n elements.helpButton.addEventListener('click', () => {\n elements.helpPanel!.classList.toggle('visible');\n });\n }\n // Fullscreen button\n if (elements.fullscreenButton) {\n // Detect iOS - iOS does not support the Fullscreen API\n const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) ||\n (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1); // iPad Pro detection\n // Hide fullscreen button on iOS since the API doesn't work there\n if (isIOS) {\n elements.fullscreenButton.style.display = 'none';\n console.log('[StorySplat Viewer] Fullscreen button hidden on iOS (API not supported)');\n }\n else {\n const container = elements.fullscreenButton.parentElement;\n elements.fullscreenButton.addEventListener('click', () => {\n // Use webkit-prefixed API for Safari compatibility\n const doc = document as WebkitFullscreenDocument;\n const fullscreenElement = doc.fullscreenElement || doc.webkitFullscreenElement;\n if (!fullscreenElement) {\n // Enter fullscreen - try standard API first, then webkit\n if (container?.requestFullscreen) {\n container.requestFullscreen();\n }\n else if ((container as WebkitFullscreenElement | null)?.webkitRequestFullscreen) {\n (container as WebkitFullscreenElement).webkitRequestFullscreen?.();\n }\n const expandIcon = elements.fullscreenButton!.querySelector('.storysplat-expand-icon') as HTMLElement;\n const compressIcon = elements.fullscreenButton!.querySelector('.storysplat-compress-icon') as HTMLElement;\n if (expandIcon)\n expandIcon.style.display = 'none';\n if (compressIcon)\n compressIcon.style.display = 'block';\n }\n else {\n // Exit fullscreen - try standard API first, then webkit\n if (doc.exitFullscreen) {\n doc.exitFullscreen();\n }\n else if (doc.webkitExitFullscreen) {\n doc.webkitExitFullscreen();\n }\n const expandIcon = elements.fullscreenButton!.querySelector('.storysplat-expand-icon') as HTMLElement;\n const compressIcon = elements.fullscreenButton!.querySelector('.storysplat-compress-icon') as HTMLElement;\n if (expandIcon)\n expandIcon.style.display = 'block';\n if (compressIcon)\n compressIcon.style.display = 'none';\n }\n });\n // Listen for fullscreen change events (including webkit prefix)\n const handleFullscreenChange = () => {\n const doc = document as WebkitFullscreenDocument;\n const isFullscreen = doc.fullscreenElement || doc.webkitFullscreenElement;\n const expandIcon = elements.fullscreenButton!.querySelector('.storysplat-expand-icon') as HTMLElement;\n const compressIcon = elements.fullscreenButton!.querySelector('.storysplat-compress-icon') as HTMLElement;\n if (expandIcon)\n expandIcon.style.display = isFullscreen ? 'none' : 'block';\n if (compressIcon)\n compressIcon.style.display = isFullscreen ? 'block' : 'none';\n };\n document.addEventListener('fullscreenchange', handleFullscreenChange);\n document.addEventListener('webkitfullscreenchange', handleFullscreenChange);\n }\n }\n // Helper to show/hide tour navigation controls based on mode\n const setTourControlsVisible = (visible: boolean) => {\n const progressText = elements.scrollControls?.querySelector('.storysplat-progress-text') as HTMLElement;\n const progressContainer = elements.scrollControls?.querySelector('.storysplat-progress-container') as HTMLElement;\n const scrollButtons = elements.scrollControls?.querySelector('.storysplat-scroll-buttons') as HTMLElement;\n const display = visible ? '' : 'none';\n if (progressText)\n progressText.style.display = display;\n if (progressContainer)\n progressContainer.style.display = display;\n if (scrollButtons)\n scrollButtons.style.display = display;\n };\n // Helper to show/hide explore controls (orbit/fly buttons)\n const setExploreControlsVisible = (visible: boolean) => {\n const exploreControls = elements.scrollControls?.querySelector('.storysplat-explore-controls') as HTMLElement;\n if (exploreControls) {\n if (visible) {\n exploreControls.classList.add('visible');\n }\n else {\n exploreControls.classList.remove('visible');\n }\n }\n };\n // Helper to update orbit/fly button selected state\n const updateExploreButtons = (activeMode: 'orbit' | 'fly') => {\n const exploreBtns = elements.scrollControls?.querySelectorAll('.storysplat-explore-btn');\n exploreBtns?.forEach(btn => {\n const mode = btn.getAttribute('data-explore-mode');\n btn.classList.toggle('selected', mode === activeMode);\n });\n };\n // Set initial visibility based on default mode\n setTourControlsVisible(defaultCameraMode === 'tour');\n setExploreControlsVisible(defaultCameraMode === 'explore');\n // Mode toggle buttons\n // Query from the container (parent) since pro template moves mode buttons out of scrollControls\n if (viewer.setCameraMode) {\n const modeButtonContainer = elements.scrollControls?.parentElement || elements.scrollControls;\n const modeButtons = modeButtonContainer?.querySelectorAll('.storysplat-mode-btn');\n modeButtons?.forEach(btn => {\n btn.addEventListener('click', () => {\n const mode = btn.getAttribute('data-mode');\n if (mode && viewer.setCameraMode) {\n viewer.setCameraMode(mode);\n // Update selected state\n modeButtons.forEach(b => b.classList.remove('selected'));\n btn.classList.add('selected');\n // Show/hide tour vs explore controls based on mode\n setTourControlsVisible(mode === 'tour');\n setExploreControlsVisible(mode === 'explore');\n }\n });\n });\n // Listen for mode changes\n viewer.on('modeChange', ({ mode }: {\n mode: string;\n }) => {\n setTourControlsVisible(mode === 'tour');\n setExploreControlsVisible(mode === 'explore');\n // Update button selected state\n modeButtons?.forEach(btn => {\n const btnMode = btn.getAttribute('data-mode');\n btn.classList.toggle('selected', btnMode === mode);\n });\n });\n }\n // Update progress continuously (throttled to prevent visual artifacts)\n let lastProgressUpdate = 0;\n let lastDisplayedPercentage = -1;\n viewer.on('progressUpdate', ({ progress }: {\n progress: number;\n }) => {\n // Clamp progress to 0-1 range\n const clampedProgress = Math.max(0, Math.min(1, progress));\n const percentage = clampedProgress * 100;\n const roundedPercentage = Math.round(percentage);\n // Throttle UI updates to ~30fps to prevent visual glitches\n const now = performance.now();\n if (now - lastProgressUpdate < 33)\n return;\n lastProgressUpdate = now;\n if (elements.progressBar) {\n elements.progressBar.style.width = `${percentage}%`;\n }\n // Only update text if the percentage actually changed (prevents flickering)\n if (elements.progressText && roundedPercentage !== lastDisplayedPercentage) {\n lastDisplayedPercentage = roundedPercentage;\n // Clear and set to ensure no duplicate content\n elements.progressText.innerHTML = '';\n elements.progressText.textContent = formatPercentageLabel(buttonLabels, roundedPercentage);\n }\n });\n // Update waypoint info panel when waypoint changes\n let lastDisplayedWaypointIndex = -1;\n viewer.on('waypointChange', ({ index, waypoint }: {\n index: number;\n waypoint?: WaypointData;\n }) => {\n // Only update if waypoint actually changed\n if (index === lastDisplayedWaypointIndex)\n return;\n lastDisplayedWaypointIndex = index;\n if (!elements.waypointInfo)\n return;\n const titleEl = elements.waypointInfo.querySelector('.storysplat-waypoint-title') as HTMLElement;\n const descEl = elements.waypointInfo.querySelector('.storysplat-waypoint-description') as HTMLElement;\n if (!titleEl || !descEl)\n return;\n // Get waypoint data - either from event or from viewer\n const wp = waypoint || (viewer.getWaypoints?.()[index]);\n if (wp && (wp.name || wp.info)) {\n // Show waypoint name as title\n titleEl.textContent = wp.name || '';\n // Show waypoint info as description\n descEl.textContent = wp.info || '';\n // Show panel if there's content\n if (wp.name || wp.info) {\n elements.waypointInfo.classList.add('hasContent');\n }\n else {\n elements.waypointInfo.classList.remove('hasContent');\n }\n }\n else {\n // Hide panel if no content\n titleEl.textContent = '';\n descEl.textContent = '';\n elements.waypointInfo.classList.remove('hasContent');\n }\n });\n}\n/**\n * Show hotspot popup with content - matching BabylonJS HTML export behavior\n */\nexport function showHotspotPopup(container: HTMLElement, hotspot: HotspotData, buttonLabels?: ButtonLabels): void {\n const popup = container.querySelector('.storysplat-hotspot-popup') as HTMLElement;\n if (!popup)\n return;\n const titleEl = popup.querySelector('.storysplat-hotspot-popup-title') as HTMLElement;\n const contentEl = popup.querySelector('.storysplat-hotspot-popup-content') as HTMLElement;\n const closeBtn = popup.querySelector('.storysplat-hotspot-popup-close') as HTMLElement;\n // Reset any previous custom styles and classes\n popup.style.cssText = '';\n popup.classList.remove('fullscreen');\n // Determine activation mode (default to 'click')\n const activationMode = hotspot.activationMode || 'click';\n const hasMediaContent = hotspot.photoUrl || hotspot.popupVideoUrl || (hotspot.contentType === 'iframe' && hotspot.iframeUrl);\n // Apply fullscreen mode for click activation with photo, video, or iframe (matching BabylonJS export)\n if (activationMode === 'click' && hasMediaContent) {\n popup.classList.add('fullscreen');\n }\n // Apply custom styling if provided (matching BabylonJS export)\n // Combine background color with alpha (default 0.75 opacity)\n const bgAlpha = hotspot.backgroundAlpha ?? 0.75;\n if (hotspot.backgroundColor) {\n const hex = hotspot.backgroundColor.replace('#', '');\n if (hex.length === 6) {\n const r = parseInt(hex.substring(0, 2), 16);\n const g = parseInt(hex.substring(2, 4), 16);\n const b = parseInt(hex.substring(4, 6), 16);\n popup.style.backgroundColor = `rgba(${r}, ${g}, ${b}, ${bgAlpha})`;\n } else {\n popup.style.backgroundColor = hotspot.backgroundColor;\n }\n } else {\n // Default black background with custom alpha\n popup.style.backgroundColor = `rgba(0, 0, 0, ${bgAlpha})`;\n }\n if (hotspot.textColor) {\n popup.style.color = hotspot.textColor;\n if (titleEl)\n titleEl.style.color = hotspot.textColor;\n }\n if (hotspot.fontFamily) {\n popup.style.fontFamily = hotspot.fontFamily;\n }\n if (hotspot.fontSize) {\n popup.style.fontSize = `${hotspot.fontSize}px`;\n }\n // Apply close button color if provided\n if (closeBtn && hotspot.closeButtonColor) {\n closeBtn.style.backgroundColor = hotspot.closeButtonColor;\n }\n // Set title\n if (titleEl) {\n titleEl.textContent = hotspot.title || getButtonLabel(buttonLabels, 'hotspotDefaultTitle');\n }\n // Build content HTML - order matches BabylonJS export: title, iframe, photo, info, link\n let contentHtml = '';\n // Iframe content (before photo in BabylonJS export)\n if (hotspot.contentType === 'iframe' && hotspot.iframeUrl) {\n contentHtml += `<iframe src=\"${hotspot.iframeUrl}\" title=\"${hotspot.title || 'Embedded content'}\"></iframe>`;\n }\n // Video content (embedded video player in popup) - wrapped in container for sizing\n if (hotspot.popupVideoUrl) {\n contentHtml += `<div class=\"video-container\"><video src=\"${hotspot.popupVideoUrl}\" controls playsinline webkit-playsinline preload=\"metadata\"></video></div>`;\n }\n // Image content\n if (hotspot.photoUrl) {\n contentHtml += `<img src=\"${hotspot.photoUrl}\" alt=\"${hotspot.title || 'Hotspot image'}\" />`;\n }\n // Information text\n if (hotspot.information) {\n contentHtml += `<p>${hotspot.information}</p>`;\n }\n // External link\n if (hotspot.externalLinkUrl) {\n const btnColor = hotspot.externalLinkButtonColor || '#007bff';\n contentHtml += `\n <div onclick=\"window.open('${hotspot.externalLinkUrl}', '_blank', 'noopener,noreferrer')\"\n class=\"storysplat-hotspot-popup-link\" style=\"background-color: ${btnColor}\">\n ${hotspot.externalLinkText || getButtonLabel(buttonLabels, 'openExternalLink')}\n </div>\n `;\n }\n if (contentEl) {\n contentEl.innerHTML = contentHtml;\n }\n // Show popup (no overlay - matching BabylonJS export)\n popup.classList.add('visible');\n}\n/**\n * Hide hotspot popup\n */\nexport function hideHotspotPopup(container: HTMLElement): void {\n const popup = container.querySelector('.storysplat-hotspot-popup') as HTMLElement;\n if (popup)\n popup.classList.remove('visible', 'fullscreen');\n}\n/**\n * Show/hide virtual joystick overlay\n */\nexport function setJoystickVisible(elements: UIElements, visible: boolean): void {\n if (elements.joystick) {\n if (visible) {\n elements.joystick.classList.add('visible');\n // Reset thumb position to prevent stuck state\n if (elements.joystickThumb) {\n elements.joystickThumb.classList.remove('active');\n elements.joystickThumb.style.transform = 'translate(0, 0)';\n }\n }\n else {\n elements.joystick.classList.remove('visible');\n }\n }\n if (elements.lookZone) {\n if (visible) {\n elements.lookZone.classList.add('visible');\n }\n else {\n elements.lookZone.classList.remove('visible');\n }\n }\n}\n/**\n * Update joystick thumb position based on touch input\n */\nexport function updateJoystickPosition(elements: UIElements, active: boolean, dx: number, dy: number, maxRadius: number): void {\n if (!elements.joystickThumb)\n return;\n if (active) {\n elements.joystickThumb.classList.add('active');\n // Clamp the offset to maxRadius\n const distance = Math.sqrt(dx * dx + dy * dy);\n const clampedDistance = Math.min(distance, maxRadius);\n const scale = distance > 0 ? clampedDistance / distance : 0;\n const clampedX = dx * scale;\n const clampedY = dy * scale;\n elements.joystickThumb.style.transform = `translate(${clampedX}px, ${clampedY}px)`;\n }\n else {\n elements.joystickThumb.classList.remove('active');\n elements.joystickThumb.style.transform = 'translate(0, 0)';\n }\n}\n/**\n * Update look zone visual state based on touch input\n */\nexport function updateLookZoneState(elements: UIElements, active: boolean): void {\n if (!elements.lookZone)\n return;\n if (active) {\n elements.lookZone.classList.add('active');\n }\n else {\n elements.lookZone.classList.remove('active');\n }\n}\n/**\n * Update active waypoint in the waypoint list dropdown\n */\nexport function updateWaypointListActive(elements: UIElements, index: number): void {\n if (!elements.waypointListContainer)\n return;\n const items = elements.waypointListContainer.querySelectorAll('.storysplat-waypoint-item');\n items.forEach((item, i) => {\n if (i === index) {\n item.classList.add('active');\n }\n else {\n item.classList.remove('active');\n }\n });\n}\n/**\n * Setup waypoint list click handlers\n */\nexport function setupWaypointListClickHandlers(elements: UIElements, onWaypointClick: (index: number) => void): void {\n if (!elements.waypointListContainer)\n return;\n const items = elements.waypointListContainer.querySelectorAll('.storysplat-waypoint-item');\n const toggleBtn = elements.waypointListContainer.querySelector('.storysplat-waypoint-list-toggle');\n const dropdown = elements.waypointListContainer.querySelector('.storysplat-waypoint-list-dropdown');\n items.forEach((item) => {\n item.addEventListener('click', () => {\n const index = parseInt(item.getAttribute('data-waypoint-index') || '0', 10);\n onWaypointClick(index);\n // Close dropdown after selection\n toggleBtn?.classList.remove('open');\n dropdown?.classList.remove('open');\n });\n });\n}\n/**\n * Lazy load UI options\n */\nexport interface LazyLoadUIOptions {\n thumbnailUrl?: string;\n /**\n * Type of media for the thumbnail.\n * - 'image': Static image (jpg, png, webp)\n * - 'video': Video file (mp4, webm) - will autoplay muted and loop\n * - 'gif': Animated GIF\n * If not specified, auto-detects from URL extension\n */\n thumbnailType?: 'image' | 'video' | 'gif';\n buttonText?: string;\n uiColor?: string;\n onStart: () => void;\n}\n/**\n * Detect media type from URL extension\n */\nfunction detectMediaType(url: string): 'image' | 'video' | 'gif' {\n const lowerUrl = url.toLowerCase();\n // Check for video extensions\n if (lowerUrl.includes('.mp4') || lowerUrl.includes('.webm') || lowerUrl.includes('.mov') || lowerUrl.includes('.ogg')) {\n return 'video';\n }\n // Check for GIF\n if (lowerUrl.includes('.gif')) {\n return 'gif';\n }\n // Default to image\n return 'image';\n}\n/**\n * Create lazy load UI with thumbnail and start button\n * Supports image, video, and GIF thumbnails\n * Returns the container element for later removal\n */\nexport function createLazyLoadUI(container: HTMLElement, options: LazyLoadUIOptions): HTMLElement {\n const { thumbnailUrl, thumbnailType, buttonText = 'Start Experience', uiColor = '#4CAF50', onStart } = options;\n // Inject styles if not already present\n injectStyles(uiColor);\n // Add container class\n container.classList.add('storysplat-viewer-container');\n // Create lazy load container\n const lazyLoadContainer = document.createElement('div');\n lazyLoadContainer.className = 'storysplat-lazy-load-container';\n // Determine media type (auto-detect if not specified)\n const mediaType = thumbnailType || (thumbnailUrl ? detectMediaType(thumbnailUrl) : 'image');\n // Build HTML content\n let html = '';\n // Thumbnail if provided - supports image, video, and GIF\n if (thumbnailUrl) {\n if (mediaType === 'video') {\n // Video thumbnail - autoplay, muted, loop for background video effect\n html += `<video class=\"storysplat-lazy-load-thumbnail-video\" src=\"${thumbnailUrl}\" autoplay muted loop playsinline webkit-playsinline></video>`;\n }\n else {\n // Image or GIF thumbnail (GIFs work naturally with img tags)\n html += `<img class=\"storysplat-lazy-load-thumbnail\" src=\"${thumbnailUrl}\" alt=\"Scene preview\" />`;\n }\n }\n // Overlay gradient\n html += `<div class=\"storysplat-lazy-load-overlay\"></div>`;\n // Content with start button\n html += `\n <div class=\"storysplat-lazy-load-content\">\n <button class=\"storysplat-lazy-load-start-btn\" style=\"background: ${uiColor}\">\n <svg viewBox=\"0 0 24 24\">\n <path d=\"M8 5v14l11-7z\"/>\n </svg>\n ${buttonText}\n </button>\n </div>\n `;\n lazyLoadContainer.innerHTML = html;\n container.appendChild(lazyLoadContainer);\n // Add click handler to start button\n const startBtn = lazyLoadContainer.querySelector('.storysplat-lazy-load-start-btn');\n startBtn?.addEventListener('click', () => {\n // Stop video playback if present before removing\n const video = lazyLoadContainer.querySelector('video');\n if (video) {\n video.pause();\n video.src = ''; // Clear source to stop loading\n }\n // Fade out and remove\n lazyLoadContainer.style.transition = 'opacity 0.3s ease-out';\n lazyLoadContainer.style.opacity = '0';\n setTimeout(() => {\n lazyLoadContainer.remove();\n onStart();\n }, 300);\n });\n return lazyLoadContainer;\n}\n","/**\n * CameraControls - PlayCanvas Camera Controller\n *\n * Ported from PlayCanvas engine scripts/esm/camera-controls.mjs\n * Provides orbit, fly, and focus camera modes with mouse, touch, and gamepad support.\n *\n * Controls:\n * - LMB drag: Orbit around focus point\n * - RMB drag + WASD: Fly mode\n * - Shift + drag / MMB: Pan\n * - Scroll / Pinch: Zoom\n * - Double-click: Focus on point (handled externally)\n */\n\nimport * as pc from 'playcanvas';\n\ntype CameraComponentWithSystem = Omit<pc.CameraComponent, 'system'> & {\n system: {\n app?: pc.Application;\n };\n};\n\ntype FocusControllerWithComplete = Omit<pc.FocusController, 'complete'> & {\n complete?: () => boolean;\n};\n\ntype AppWithXR = Omit<pc.Application, 'xr'> & {\n xr?: {\n active?: boolean;\n };\n};\n\ninterface CollisionEntity extends pc.Entity {\n _collisionMeshType?: string;\n _collisionBounds?: pc.BoundingBox;\n}\n\nconst tmpV1 = new pc.Vec3();\nconst tmpV2 = new pc.Vec3();\nconst pose = new pc.Pose();\nconst frame = new pc.InputFrame({\n move: [0, 0, 0],\n rotate: [0, 0, 0]\n});\n\n/**\n * Calculate the damp rate for smooth interpolation\n */\nexport const damp = (damping: number, dt: number): number => 1 - Math.pow(damping, dt * 1000);\n\n/**\n * Apply dead zone to gamepad stick input\n */\nconst applyDeadZone = (stick: number[], low: number, high: number): void => {\n const mag = Math.sqrt(stick[0] * stick[0] + stick[1] * stick[1]);\n if (mag < low) {\n stick.fill(0);\n return;\n }\n const scale = (mag - low) / (high - low);\n stick[0] *= scale / mag;\n stick[1] *= scale / mag;\n};\n\n/**\n * Converts screen space mouse deltas to world space pan vector\n */\nconst screenToWorld = (\n camera: pc.CameraComponent,\n dx: number,\n dy: number,\n dz: number,\n out: pc.Vec3 = new pc.Vec3()\n): pc.Vec3 => {\n const { fov, aspectRatio, horizontalFov, projection, orthoHeight } = camera;\n const app = (camera as unknown as CameraComponentWithSystem).system?.app;\n const { width, height } = app?.graphicsDevice?.clientRect || { width: 1920, height: 1080 };\n\n // normalize deltas to device coord space\n out.set(\n -(dx / width) * 2,\n (dy / height) * 2,\n 0\n );\n\n // calculate half size of the view frustum at the current distance\n const halfSize = tmpV2.set(0, 0, 0);\n if (projection === pc.PROJECTION_PERSPECTIVE) {\n const halfSlice = dz * Math.tan(0.5 * fov * pc.math.DEG_TO_RAD);\n if (horizontalFov) {\n halfSize.set(halfSlice, halfSlice / aspectRatio, 0);\n } else {\n halfSize.set(halfSlice * aspectRatio, halfSlice, 0);\n }\n } else {\n halfSize.set(orthoHeight * aspectRatio, orthoHeight, 0);\n }\n\n // scale by device coord space\n out.mul(halfSize);\n return out;\n};\n\ntype CameraMode = 'orbit' | 'fly' | 'focus';\ntype MobileInputLayout = 'joystick-joystick' | 'joystick-touch' | 'touch-joystick' | 'touch-touch';\n\nexport interface CameraControlsConfig {\n enableOrbit?: boolean;\n enableFly?: boolean;\n enablePan?: boolean;\n focusPoint?: pc.Vec3;\n moveSpeed?: number;\n moveFastSpeed?: number;\n moveSlowSpeed?: number;\n rotateSpeed?: number;\n rotateTouchSens?: number;\n rotateJoystickSens?: number;\n zoomSpeed?: number;\n zoomPinchSens?: number;\n focusDamping?: number;\n rotateDamping?: number;\n moveDamping?: number;\n zoomDamping?: number;\n pitchRange?: pc.Vec2;\n yawRange?: pc.Vec2;\n zoomRange?: pc.Vec2;\n gamepadDeadZone?: pc.Vec2;\n mobileInputLayout?: MobileInputLayout;\n /** Invert camera rotation (Y-axis) for inverted mouse/touch controls */\n invertRotation?: boolean;\n}\n\n/**\n * CameraControls class - Manages camera with orbit, fly, and focus modes\n */\nexport class CameraControls {\n private camera: pc.Entity;\n private cameraComponent: pc.CameraComponent;\n private app: pc.Application;\n private enabled: boolean = true;\n\n // Mode state\n private _mode: CameraMode = 'orbit';\n private _enableOrbit: boolean = true;\n private _enableFly: boolean = true;\n enablePan: boolean = true;\n\n // Controllers\n private _flyController: pc.FlyController;\n private _orbitController: pc.OrbitController;\n private _focusController: pc.FocusController;\n private _controller: pc.InputController;\n private _pose: pc.Pose = new pc.Pose();\n private _preFocusMode: CameraMode = 'orbit';\n\n // Input sources\n private _desktopInput: pc.KeyboardMouseSource;\n private _orbitMobileInput: pc.MultiTouchSource;\n private _flyMobileInput: pc.DualGestureSource;\n private _gamepadInput: pc.GamepadSource;\n\n // Zoom\n private _startZoomDist: number = 0;\n // Limit pitch to ±89° to avoid gimbal lock (world flip at ±90°)\n private _pitchRange: pc.Vec2 = new pc.Vec2(-89, 89);\n private _yawRange: pc.Vec2 = new pc.Vec2(-Infinity, Infinity);\n\n // Store the last focus point for orbit mode\n private _lastFocusPoint: pc.Vec3 = new pc.Vec3(0, 0, 0);\n private _zoomRange: pc.Vec2 = new pc.Vec2(0.01, Infinity);\n\n // Debug: track last yaw for discontinuity detection\n private _lastYaw?: number;\n\n // State tracking\n private _state = {\n axis: new pc.Vec3(),\n shift: 0,\n ctrl: 0,\n mouse: [0, 0, 0],\n touches: 0\n };\n\n // Speed settings\n moveSpeed: number = 25;\n moveFastSpeed: number = 50;\n moveSlowSpeed: number = 10;\n rotateSpeed: number = 0.05;\n rotateTouchSens: number = 1.0; // Touch sensitivity (matches desktop)\n rotateJoystickSens: number = 1;\n zoomSpeed: number = 0.001;\n zoomPinchSens: number = 5;\n keyboardSpeedMultiplier: number = 1.5; // Desktop keyboard moves faster than mobile joystick\n gamepadDeadZone: pc.Vec2 = new pc.Vec2(0.3, 0.6);\n /** Invert camera rotation (Y-axis) */\n invertRotation: boolean = false;\n\n // Joystick event name for UI\n joystickEventName: string = 'joystick';\n\n // Collision detection for fly mode\n private _collisionEntities: pc.Entity[] = [];\n private _collisionRadius: number = 0.3;\n private _prevPosition: pc.Vec3 = new pc.Vec3();\n private _prevPositionValid: boolean = false;\n private _collisionTestVec: pc.Vec3 = new pc.Vec3();\n\n // Cleanup handlers\n private _destroyHandler: (() => void) | null = null;\n\n constructor(camera: pc.Entity, app: pc.Application, config: CameraControlsConfig = {}) {\n this.camera = camera;\n this.app = app;\n\n const cameraComponent = camera.camera;\n if (!cameraComponent) {\n throw new Error('CameraControls: camera component not found on entity');\n }\n this.cameraComponent = cameraComponent;\n\n // Initialize controllers\n this._flyController = new pc.FlyController();\n this._orbitController = new pc.OrbitController();\n this._focusController = new pc.FocusController();\n\n // Set default dampening values for responsive BabylonJS-like feel\n // Lower values = more responsive, less \"floaty\" (0.75 matches BabylonJS snappiness better than 0.9)\n this._flyController.moveDamping = 0.75;\n this._flyController.rotateDamping = 0.75;\n this._orbitController.rotateDamping = 0.75;\n this._orbitController.zoomDamping = 0.8;\n\n // Set orbit controller defaults\n this._orbitController.zoomRange = new pc.Vec2(0.01, Infinity);\n\n // Initialize input sources\n const canvas = app.graphicsDevice.canvas;\n this._desktopInput = new pc.KeyboardMouseSource();\n this._orbitMobileInput = new pc.MultiTouchSource();\n this._flyMobileInput = new pc.DualGestureSource();\n this._gamepadInput = new pc.GamepadSource();\n\n // Attach input sources to canvas\n this._desktopInput.attach(canvas);\n this._orbitMobileInput.attach(canvas);\n this._flyMobileInput.attach(canvas);\n this._gamepadInput.attach(canvas);\n\n // Expose UI joystick events\n this._flyMobileInput.on('joystick:position:left', ([bx, by, sx, sy]: number[]) => {\n // Always fire release events (bx < 0) to prevent stuck joystick UI\n if (bx < 0 || this._mode === 'fly') {\n this.app.fire(`${this.joystickEventName}:left`, bx, by, sx, sy);\n }\n });\n this._flyMobileInput.on('joystick:position:right', ([bx, by, sx, sy]: number[]) => {\n if (bx < 0 || this._mode === 'fly') {\n this.app.fire(`${this.joystickEventName}:right`, bx, by, sx, sy);\n }\n });\n\n // Initialize pose from camera position\n this._pose.look(this.camera.getPosition(), pc.Vec3.ZERO);\n\n // Set initial mode\n this._setMode('orbit');\n\n // Store controller reference\n this._controller = this._orbitController;\n\n // Apply config\n if (config.enableOrbit !== undefined) this.enableOrbit = config.enableOrbit;\n if (config.enableFly !== undefined) this.enableFly = config.enableFly;\n if (config.enablePan !== undefined) this.enablePan = config.enablePan;\n if (config.focusPoint) this.focusPoint = config.focusPoint;\n if (config.moveSpeed !== undefined) this.moveSpeed = config.moveSpeed;\n if (config.moveFastSpeed !== undefined) this.moveFastSpeed = config.moveFastSpeed;\n if (config.moveSlowSpeed !== undefined) this.moveSlowSpeed = config.moveSlowSpeed;\n if (config.rotateSpeed !== undefined) this.rotateSpeed = config.rotateSpeed;\n if (config.rotateTouchSens !== undefined) this.rotateTouchSens = config.rotateTouchSens;\n if (config.rotateJoystickSens !== undefined) this.rotateJoystickSens = config.rotateJoystickSens;\n if (config.zoomSpeed !== undefined) this.zoomSpeed = config.zoomSpeed;\n if (config.zoomPinchSens !== undefined) this.zoomPinchSens = config.zoomPinchSens;\n if (config.focusDamping !== undefined) this.focusDamping = config.focusDamping;\n if (config.rotateDamping !== undefined) this.rotateDamping = config.rotateDamping;\n if (config.moveDamping !== undefined) this.moveDamping = config.moveDamping;\n if (config.zoomDamping !== undefined) this.zoomDamping = config.zoomDamping;\n if (config.pitchRange) this.pitchRange = config.pitchRange;\n if (config.yawRange) this.yawRange = config.yawRange;\n if (config.zoomRange) this.zoomRange = config.zoomRange;\n if (config.gamepadDeadZone) this.gamepadDeadZone = config.gamepadDeadZone;\n if (config.mobileInputLayout) this.mobileInputLayout = config.mobileInputLayout;\n if (config.invertRotation !== undefined) this.invertRotation = config.invertRotation;\n }\n\n // Enable/disable getters and setters\n set enableFly(enable: boolean) {\n this._enableFly = enable;\n if (!this._enableFly && this._mode === 'fly') {\n this._setMode('orbit');\n }\n }\n\n get enableFly(): boolean {\n return this._enableFly;\n }\n\n set enableOrbit(enable: boolean) {\n this._enableOrbit = enable;\n if (!this._enableOrbit && this._mode === 'orbit') {\n this._setMode('fly');\n }\n }\n\n get enableOrbit(): boolean {\n return this._enableOrbit;\n }\n\n // Damping getters/setters\n set focusDamping(damping: number) {\n this._focusController.focusDamping = damping;\n }\n\n get focusDamping(): number {\n return this._focusController.focusDamping;\n }\n\n set moveDamping(damping: number) {\n this._flyController.moveDamping = damping;\n }\n\n get moveDamping(): number {\n return this._flyController.moveDamping;\n }\n\n set rotateDamping(damping: number) {\n this._flyController.rotateDamping = damping;\n this._orbitController.rotateDamping = damping;\n }\n\n get rotateDamping(): number {\n return this._orbitController.rotateDamping;\n }\n\n set zoomDamping(damping: number) {\n this._orbitController.zoomDamping = damping;\n }\n\n get zoomDamping(): number {\n return this._orbitController.zoomDamping;\n }\n\n // Focus point getter/setter\n set focusPoint(point: pc.Vec3) {\n const position = this.camera.getPosition();\n this._startZoomDist = position.distance(point);\n this._controller.attach(this._pose.look(position, point), false);\n }\n\n get focusPoint(): pc.Vec3 {\n return this._pose.getFocus(tmpV1);\n }\n\n // Range getters/setters\n set pitchRange(range: pc.Vec2) {\n this._pitchRange.copy(range);\n this._flyController.pitchRange = this._pitchRange;\n this._orbitController.pitchRange = this._pitchRange;\n }\n\n get pitchRange(): pc.Vec2 {\n return this._pitchRange;\n }\n\n set yawRange(range: pc.Vec2) {\n this._yawRange.x = pc.math.clamp(range.x, -360, 360);\n this._yawRange.y = pc.math.clamp(range.y, -360, 360);\n this._flyController.yawRange = this._yawRange;\n this._orbitController.yawRange = this._yawRange;\n }\n\n get yawRange(): pc.Vec2 {\n return this._yawRange;\n }\n\n set zoomRange(range: pc.Vec2) {\n this._zoomRange.x = range.x;\n this._zoomRange.y = range.y <= range.x ? Infinity : range.y;\n this._orbitController.zoomRange = this._zoomRange;\n }\n\n get zoomRange(): pc.Vec2 {\n return this._zoomRange;\n }\n\n // Mobile input layout\n set mobileInputLayout(layout: MobileInputLayout) {\n if (!/(?:joystick|touch)-(?:joystick|touch)/.test(layout)) {\n console.warn(`CameraControls: invalid mobile input layout: ${layout}`);\n return;\n }\n this._flyMobileInput.layout = layout;\n }\n\n get mobileInputLayout(): MobileInputLayout {\n return this._flyMobileInput.layout as MobileInputLayout;\n }\n\n /**\n * Get current camera mode\n */\n get mode(): CameraMode {\n return this._mode;\n }\n\n /**\n * Set camera mode\n */\n private _setMode(mode: CameraMode): void {\n // Override mode depending on enabled features\n if (this._enableFly && !this._enableOrbit) {\n mode = 'fly';\n } else if (!this._enableFly && this._enableOrbit) {\n mode = 'orbit';\n } else if (!this._enableFly && !this._enableOrbit) {\n console.warn('CameraControls: both fly and orbit modes are disabled');\n return;\n }\n\n const previousMode = this._mode;\n if (previousMode === mode) return;\n this._mode = mode;\n\n // Detach old controller\n if (this._controller) {\n this._controller.detach();\n }\n\n // Attach new controller\n switch (this._mode) {\n case 'orbit':\n this._controller = this._orbitController;\n // When switching to orbit (especially after focus), set up orbit around the last focus point\n if (previousMode === 'focus') {\n const position = this.camera.getPosition();\n this._pose.look(position, this._lastFocusPoint);\n }\n // Reset yaw tracking to prevent false discontinuity detection on mode switch\n this._lastYaw = this._pose.angles.y;\n break;\n case 'fly':\n this._controller = this._flyController;\n // When returning to fly after focus, sync pose from current camera position\n if (previousMode === 'focus') {\n const position = this.camera.getPosition();\n this._pose.look(position, this._lastFocusPoint);\n }\n break;\n case 'focus':\n this._controller = this._focusController;\n break;\n }\n this._controller.attach(this._pose, false);\n\n // Emit mode change event for UI updates\n this.app.fire('cameracontrols:modechange', this._mode);\n }\n\n /**\n * Public method to set camera mode (orbit or fly only)\n * Used by UI toggle buttons to switch between orbit and fly modes\n */\n setMode(mode: 'orbit' | 'fly'): void {\n this._setMode(mode);\n }\n\n /**\n * Focus camera on a point with smooth animation.\n * After the focus animation completes, orbit mode will use this point as the orbit center.\n */\n focus(focusPoint: pc.Vec3, resetZoom: boolean = false): void {\n // Store the focus point so orbit mode uses it as the center\n this._lastFocusPoint.copy(focusPoint);\n\n // Remember current mode to restore after focus completes\n if (this._mode !== 'focus') {\n this._preFocusMode = this._mode;\n }\n this._setMode('focus');\n const zoomDist = resetZoom\n ? this._startZoomDist\n : this.camera.getPosition().distance(focusPoint);\n this._startZoomDist = zoomDist; // Update the zoom distance for orbit mode\n const position = tmpV1.copy(this.camera.forward).mulScalar(-zoomDist).add(focusPoint);\n this._controller.attach(pose.look(position, focusPoint));\n }\n\n /**\n * Fly the camera toward a target point, stopping slightly before the surface.\n * Used for double-click/tap in fly mode to move the camera to the clicked point.\n */\n flyTo(targetPoint: pc.Vec3, stopDistance: number = 2): void {\n this._lastFocusPoint.copy(targetPoint);\n if (this._mode !== 'focus') {\n this._preFocusMode = this._mode;\n }\n this._setMode('focus');\n\n // Calculate stop position: move close to target, stopping stopDistance units away\n const cameraPos = this.camera.getPosition();\n const totalDist = cameraPos.distance(targetPoint);\n const actualStopDist = Math.min(stopDistance, totalDist * 0.1);\n\n // Position = targetPoint offset back toward camera by stopDistance\n const direction = tmpV1.copy(targetPoint).sub(cameraPos).normalize();\n const position = tmpV2.copy(targetPoint).sub(direction.mulScalar(actualStopDist));\n\n this._controller.attach(pose.look(position, targetPoint));\n }\n\n /**\n * Look at a point without moving\n */\n look(focusPoint: pc.Vec3, resetZoom: boolean = false): void {\n if (this._mode !== 'focus') {\n this._preFocusMode = this._mode;\n }\n this._setMode('focus');\n const position = resetZoom\n ? tmpV1.copy(this.camera.getPosition()).sub(focusPoint).normalize().mulScalar(this._startZoomDist).add(focusPoint)\n : this.camera.getPosition();\n this._controller.attach(pose.look(position, focusPoint));\n }\n\n /**\n * Reset camera to a position looking at focus point\n */\n reset(focusPoint: pc.Vec3, position: pc.Vec3): void {\n if (this._mode !== 'focus') {\n this._preFocusMode = this._mode;\n }\n this._setMode('focus');\n this._controller.attach(pose.look(position, focusPoint));\n }\n\n /**\n * Sync internal pose from camera's current position and rotation.\n * If a target is provided, the pose is set up to look at that target from the current position.\n * Otherwise, preserves the camera's exact rotation.\n */\n syncFromCamera(target?: pc.Vec3): void {\n const position = this.camera.getPosition().clone();\n\n // Reset collision tracking so the first fly frame after sync just records position\n this._prevPositionValid = false;\n\n if (target) {\n // When we have a target, use pose.look() to properly set up the pose\n // This calculates correct angles to look at the target from current position\n this._lastFocusPoint.copy(target);\n const focusDistance = position.distance(target);\n this._startZoomDist = focusDistance;\n this._pose.distance = focusDistance;\n\n // Use pose.look() which properly calculates yaw/pitch to face the target\n this._pose.look(position, target);\n\n // Normalize yaw to [-180, 180] range\n let yaw = this._pose.angles.y;\n while (yaw > 180) yaw -= 360;\n while (yaw < -180) yaw += 360;\n this._pose.angles.y = yaw;\n this._lastYaw = yaw;\n\n // Clamp pitch to prevent flipping (avoid gimbal lock near ±90°)\n this._pose.angles.x = Math.max(-89, Math.min(89, this._pose.angles.x));\n\n // Ensure no roll\n this._pose.angles.z = 0;\n } else {\n // No target - preserve camera's exact rotation\n const eulerAngles = this.camera.getEulerAngles();\n\n // Set pose position directly from camera\n this._pose.position.copy(position);\n\n // Set pose angles directly from camera's euler angles\n this._pose.angles.x = eulerAngles.x;\n this._pose.angles.y = eulerAngles.y;\n this._pose.angles.z = 0; // Always zero roll to prevent flipping\n\n // Normalize yaw to [-180, 180] range\n let yaw = this._pose.angles.y;\n while (yaw > 180) yaw -= 360;\n while (yaw < -180) yaw += 360;\n this._pose.angles.y = yaw;\n this._lastYaw = yaw;\n\n // Clamp pitch to prevent flipping\n this._pose.angles.x = Math.max(-89, Math.min(89, this._pose.angles.x));\n\n // Calculate a focus point 10 units in front of the camera\n const forward = this.camera.forward.clone();\n this._lastFocusPoint.copy(position).add(forward.mulScalar(10));\n\n const focusDistance = 10;\n this._startZoomDist = focusDistance;\n this._pose.distance = focusDistance;\n }\n\n // Reattach the current controller with the synced pose\n this._controller.attach(this._pose, false);\n }\n\n /**\n * Sync internal pose from explicit position and rotation values.\n * Use this when you need to bypass the camera entity's current state.\n * Also sets the camera entity to match these values.\n */\n syncFromPose(position: pc.Vec3, rotation: pc.Quat, focusTarget?: pc.Vec3): void {\n // Reset collision tracking so the first fly frame after sync just records position\n this._prevPositionValid = false;\n\n // Set the camera entity to match the provided pose\n this.camera.setPosition(position);\n this.camera.setRotation(rotation);\n\n // Get euler angles from the quaternion\n const tempEntity = new pc.Entity();\n tempEntity.setRotation(rotation);\n const eulerAngles = tempEntity.getEulerAngles();\n\n // Set pose position\n this._pose.position.copy(position);\n\n // Set pose angles from the rotation\n this._pose.angles.x = eulerAngles.x;\n this._pose.angles.y = eulerAngles.y;\n this._pose.angles.z = 0; // Always zero roll\n\n // Normalize yaw to [-180, 180] range\n let yaw = this._pose.angles.y;\n while (yaw > 180) yaw -= 360;\n while (yaw < -180) yaw += 360;\n this._pose.angles.y = yaw;\n this._lastYaw = yaw;\n\n // Clamp pitch to prevent flipping\n this._pose.angles.x = Math.max(-89, Math.min(89, this._pose.angles.x));\n\n // Calculate focus point\n if (focusTarget) {\n this._lastFocusPoint.copy(focusTarget);\n } else {\n // Calculate a focus point 10 units in front\n const forward = this.camera.forward.clone();\n this._lastFocusPoint.copy(position).add(forward.mulScalar(10));\n }\n\n const focusDistance = position.distance(this._lastFocusPoint);\n this._startZoomDist = focusDistance;\n this._pose.distance = focusDistance;\n\n // Reattach the current controller with the synced pose\n this._controller.attach(this._pose, false);\n }\n\n /**\n * Enable controls\n */\n enable(): void {\n this.enabled = true;\n }\n\n /**\n * Disable controls\n */\n disable(): void {\n this.enabled = false;\n // Discard any pending inputs\n this._desktopInput.read();\n this._orbitMobileInput.read();\n this._flyMobileInput.read();\n this._gamepadInput.read();\n }\n\n /**\n * Update camera controls - call this every frame\n */\n update(dt: number): void {\n if (!this.enabled) return;\n\n const { keyCode } = pc.KeyboardMouseSource;\n const { key, button, mouse, wheel } = this._desktopInput.read();\n const { touch, pinch, count } = this._orbitMobileInput.read();\n const { leftInput, rightInput } = this._flyMobileInput.read();\n const { leftStick, rightStick } = this._gamepadInput.read();\n\n // Apply dead zone to gamepad sticks\n applyDeadZone(leftStick, this.gamepadDeadZone.x, this.gamepadDeadZone.y);\n applyDeadZone(rightStick, this.gamepadDeadZone.x, this.gamepadDeadZone.y);\n\n // Update state\n this._state.axis.add(tmpV1.set(\n (key[keyCode.D] - key[keyCode.A]) + (key[keyCode.RIGHT] - key[keyCode.LEFT]),\n (key[keyCode.E] - key[keyCode.Q]),\n (key[keyCode.W] - key[keyCode.S]) + (key[keyCode.UP] - key[keyCode.DOWN])\n ));\n for (let i = 0; i < this._state.mouse.length; i++) {\n this._state.mouse[i] += button[i];\n }\n this._state.shift += key[keyCode.SHIFT];\n this._state.ctrl += key[keyCode.CTRL];\n this._state.touches += count[0];\n\n // No automatic mode switching - user controls mode via UI buttons only\n\n const orbit = +(this._mode === 'orbit');\n const fly = +(this._mode === 'fly');\n const double = +(this._state.touches > 1);\n const desktopPan = +(this._state.shift || this._state.mouse[1]);\n const mobileJoystick = +(this._flyMobileInput.layout.endsWith('joystick'));\n\n // Multipliers\n const moveMult = (this._state.shift ? this.moveFastSpeed : this._state.ctrl\n ? this.moveSlowSpeed : this.moveSpeed) * dt;\n const zoomMult = this.zoomSpeed * 60 * dt;\n const zoomTouchMult = zoomMult * this.zoomPinchSens;\n const rotateMult = this.rotateSpeed * 60 * dt;\n const rotateTouchMult = rotateMult * this.rotateTouchSens;\n const rotateJoystickMult = this.rotateSpeed * this.rotateJoystickSens * 60 * dt;\n\n const { deltas } = frame;\n\n // Desktop move\n const v = tmpV1.set(0, 0, 0);\n const keyMove = this._state.axis.clone().normalize();\n v.add(keyMove.mulScalar(fly * moveMult * this.keyboardSpeedMultiplier));\n const panMove = screenToWorld(this.cameraComponent, mouse[0], mouse[1], this._pose.distance);\n v.add(panMove.mulScalar(orbit * desktopPan * +this.enablePan));\n const wheelMove = tmpV2.set(0, 0, wheel[0]);\n v.add(wheelMove.mulScalar(orbit * zoomMult));\n deltas.move.append([v.x, v.y, v.z]);\n\n // Desktop rotate\n v.set(0, 0, 0);\n const mouseRotate = tmpV2.set(mouse[0], mouse[1], 0);\n v.add(mouseRotate.mulScalar((1 - (orbit * desktopPan)) * rotateMult));\n // Apply inversion if enabled (inverts Y/pitch axis)\n if (this.invertRotation) v.y = -v.y;\n deltas.rotate.append([v.x, v.y, v.z]);\n\n // Mobile move\n v.set(0, 0, 0);\n const flyMove = tmpV2.set(leftInput[0], 0, -leftInput[1]);\n v.add(flyMove.mulScalar(fly * moveMult));\n const orbitMove = screenToWorld(this.cameraComponent, touch[0], touch[1], this._pose.distance);\n v.add(orbitMove.mulScalar(orbit * double * +this.enablePan));\n const pinchMove = tmpV2.set(0, 0, pinch[0]);\n v.add(pinchMove.mulScalar(orbit * double * zoomTouchMult));\n deltas.move.append([v.x, v.y, v.z]);\n\n // Mobile rotate (uses rotateTouchMult for reduced sensitivity on touch devices)\n v.set(0, 0, 0);\n const orbitRotate = tmpV2.set(touch[0], touch[1], 0);\n v.add(orbitRotate.mulScalar(orbit * (1 - double) * rotateTouchMult));\n const flyRotate = tmpV2.set(rightInput[0], rightInput[1], 0);\n v.add(flyRotate.mulScalar(fly * (mobileJoystick ? rotateJoystickMult : rotateTouchMult)));\n // Apply inversion if enabled (inverts Y/pitch axis)\n if (this.invertRotation) v.y = -v.y;\n deltas.rotate.append([v.x, v.y, v.z]);\n\n // Gamepad move\n v.set(0, 0, 0);\n const stickMove = tmpV2.set(leftStick[0], 0, -leftStick[1]);\n v.add(stickMove.mulScalar(fly * moveMult));\n deltas.move.append([v.x, v.y, v.z]);\n\n // Gamepad rotate\n v.set(0, 0, 0);\n const stickRotate = tmpV2.set(rightStick[0], rightStick[1], 0);\n v.add(stickRotate.mulScalar(fly * rotateJoystickMult));\n // Apply inversion if enabled (inverts Y/pitch axis)\n if (this.invertRotation) v.y = -v.y;\n deltas.rotate.append([v.x, v.y, v.z]);\n\n // Check if XR is active - discard frame if so\n if ((this.app as AppWithXR).xr?.active) {\n frame.read();\n return;\n }\n\n // Check focus end - return to previous mode (orbit or fly)\n if (this._mode === 'focus') {\n const focusInterrupt = deltas.move.length() + deltas.rotate.length() > 0;\n const focusComplete = (this._focusController as FocusControllerWithComplete).complete?.() ?? false;\n if (focusInterrupt || focusComplete) {\n this._setMode(this._preFocusMode);\n }\n }\n\n // Update controller by consuming frame\n this._pose.copy(this._controller.update(frame, dt));\n\n // Collision detection for fly and orbit modes - prevents camera from moving through collision meshes\n // Uses axis-separated checks for wall-sliding behavior (camera slides along surfaces instead of stopping)\n // In fly mode: prevents WASD movement through walls\n // In orbit mode: prevents zoom/pan from pushing camera through walls\n if ((this._mode === 'fly' || this._mode === 'orbit') && this._collisionEntities.length > 0) {\n if (this._prevPositionValid) {\n const newPos = this._pose.position;\n const prev = this._prevPosition;\n const test = this._collisionTestVec;\n let corrected = false;\n\n // Test X axis independently\n test.set(newPos.x, prev.y, prev.z);\n if (this.checkCollision(test)) {\n newPos.x = prev.x;\n corrected = true;\n }\n\n // Test Y axis independently\n test.set(newPos.x, newPos.y, prev.z);\n if (this.checkCollision(test)) {\n newPos.y = prev.y;\n corrected = true;\n }\n\n // Test Z axis independently\n test.set(newPos.x, newPos.y, newPos.z);\n if (this.checkCollision(test)) {\n newPos.z = prev.z;\n corrected = true;\n }\n\n // Re-sync controller to corrected position to prevent internal state drift\n if (corrected) {\n this._controller.attach(this._pose, false);\n }\n }\n this._prevPosition.copy(this._pose.position);\n this._prevPositionValid = true;\n } else if (this._mode !== 'fly' && this._mode !== 'orbit') {\n // Reset tracking when not in fly/orbit mode so first frame initializes correctly\n this._prevPositionValid = false;\n }\n\n // Fix yaw discontinuity in orbit mode\n // PlayCanvas's Pose.lerp() uses `% 360` after lerpAngle which doesn't handle\n // negative angles correctly (e.g., -185 % 360 = -185, not 175)\n // This causes sudden jumps when yaw crosses the ±180° boundary\n if (this._mode === 'orbit') {\n // Normalize yaw to [-180, 180) range to prevent accumulation issues\n let yaw = this._pose.angles.y;\n while (yaw > 180) yaw -= 360;\n while (yaw < -180) yaw += 360;\n\n if (this._lastYaw !== undefined) {\n // Check for discontinuous jump (more than 90° in a single frame is unnatural)\n const yawDelta = yaw - this._lastYaw;\n\n // If there's a large jump, adjust to maintain continuity\n if (yawDelta > 180) {\n yaw -= 360;\n } else if (yawDelta < -180) {\n yaw += 360;\n }\n }\n\n this._pose.angles.y = yaw;\n this._lastYaw = yaw;\n }\n\n this.camera.setPosition(this._pose.position);\n this.camera.setEulerAngles(this._pose.angles);\n }\n\n /**\n * Set collision mesh entities for explore mode collision detection.\n * Prevents the camera from passing through collision meshes in fly and orbit modes.\n */\n setCollisionEntities(entities: pc.Entity[], radius?: number): void {\n this._collisionEntities = entities;\n if (radius !== undefined) this._collisionRadius = radius;\n this._prevPositionValid = false;\n }\n\n /**\n * Check if a position collides with any collision mesh (AABB check).\n * Uses the same approach as CharacterController for consistency.\n */\n private checkCollision(position: pc.Vec3): boolean {\n const r = this._collisionRadius;\n\n for (const entity of this._collisionEntities) {\n const entityPos = entity.getPosition();\n const entityScale = entity.getLocalScale();\n const meshType = (entity as CollisionEntity)._collisionMeshType;\n\n let halfWidth: number, halfHeight: number, halfDepth: number;\n let centerX: number, centerY: number, centerZ: number;\n\n // Check for custom bounds (imported GLB/GLTF meshes)\n const customBounds = (entity as CollisionEntity)._collisionBounds;\n if (customBounds) {\n // World-space AABB - use bounds center (mesh may not be at entity origin) and don't multiply by scale\n centerX = customBounds.center.x;\n centerY = customBounds.center.y;\n centerZ = customBounds.center.z;\n halfWidth = customBounds.halfExtents.x;\n halfHeight = customBounds.halfExtents.y;\n halfDepth = customBounds.halfExtents.z;\n } else if (meshType === 'floor' || meshType === 'plane') {\n // Floors/planes are flat - use scale for width/depth, thin height\n centerX = entityPos.x;\n centerY = entityPos.y;\n centerZ = entityPos.z;\n halfWidth = entityScale.x / 2;\n halfHeight = 0.05;\n halfDepth = entityScale.z / 2;\n } else {\n // Primitive shapes (cube, sphere treated as AABB)\n centerX = entityPos.x;\n centerY = entityPos.y;\n centerZ = entityPos.z;\n halfWidth = entityScale.x / 2;\n halfHeight = entityScale.y / 2;\n halfDepth = entityScale.z / 2;\n }\n\n const dx = Math.abs(position.x - centerX);\n const dy = Math.abs(position.y - centerY);\n const dz = Math.abs(position.z - centerZ);\n\n if (dx < halfWidth + r && dy < halfHeight + r && dz < halfDepth + r) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Clean up resources\n */\n destroy(): void {\n this._desktopInput.destroy();\n this._orbitMobileInput.destroy();\n this._flyMobileInput.destroy();\n this._gamepadInput.destroy();\n\n this._flyController.destroy();\n this._orbitController.destroy();\n }\n}\n","/**\n * CharacterController - First Person Character Controller with Collisions\n *\n * A lightweight character controller for PlayCanvas that provides:\n * - WASD/Arrow key movement\n * - Mouse look\n * - Gravity and ground detection via raycasting\n * - Collision detection with scene meshes via raycasting\n *\n * Does NOT require ammo.js physics - uses simple raycasting for collisions.\n */\nimport * as pc from 'playcanvas';\n/**\n * Calculate the damp rate for smooth interpolation\n * Same formula as PlayCanvas's camera-controls for consistent feel\n */\nconst damp = (damping: number, dt: number): number => 1 - Math.pow(damping, dt * 1000);\nexport interface CharacterControllerConfig {\n /** Movement speed in units per second */\n moveSpeed?: number;\n /** Sprint speed multiplier */\n sprintMultiplier?: number;\n /** Mouse look sensitivity */\n lookSensitivity?: number;\n /** Player height (camera Y offset from ground) */\n playerHeight?: number;\n /** Gravity strength (units per second squared) */\n gravity?: number;\n /** Maximum fall speed */\n maxFallSpeed?: number;\n /** Jump velocity */\n jumpVelocity?: number;\n /** Collision radius for horizontal movement */\n collisionRadius?: number;\n /** Step height for climbing small obstacles */\n stepHeight?: number;\n /** Ground check distance below feet */\n groundCheckDistance?: number;\n /** Movement dampening for smooth start/stop (0-1, higher = more smoothing) */\n moveDamping?: number;\n}\ninterface CollisionMeshData {\n entity?: pc.Entity;\n meshType: string;\n position?: number[] | { x: number; y: number; z: number };\n rotation?: number[] | { x: number; y: number; z: number };\n scaling?: number[] | { x: number; y: number; z: number };\n customMeshUrl?: string;\n}\n/** Extract xyz from number[] or {x,y,z} format */\nfunction toXYZ(v: number[] | { x: number; y: number; z: number } | undefined, fallback: [number, number, number]): [number, number, number] {\n if (!v) return fallback;\n if (Array.isArray(v)) return [v[0] ?? fallback[0], v[1] ?? fallback[1], v[2] ?? fallback[2]];\n return [v.x ?? fallback[0], v.y ?? fallback[1], v.z ?? fallback[2]];\n}\ninterface CollisionEntity extends pc.Entity {\n _collisionBounds?: pc.BoundingBox;\n _collisionMeshType?: string;\n}\n/**\n * CharacterController class - First person controller with collision detection\n */\nexport class CharacterController {\n private camera: pc.Entity;\n private app: pc.Application;\n private enabled: boolean = false;\n // Movement state\n private velocity: pc.Vec3 = new pc.Vec3();\n private isGrounded: boolean = false;\n private yaw: number = 0;\n private pitch: number = 0;\n // Input state\n private keys: {\n [key: string]: boolean;\n } = {};\n private mouseLocked: boolean = false;\n // Configuration\n moveSpeed: number = 8; // Increased for faster movement\n sprintMultiplier: number = 2;\n lookSensitivity: number = 0.002;\n playerHeight: number = 1.6;\n gravity: number = 20;\n maxFallSpeed: number = 50;\n jumpVelocity: number = 8;\n collisionRadius: number = 0.3;\n stepHeight: number = 0.3;\n groundCheckDistance: number = 0.1;\n moveDamping: number = 0.9; // Smooth movement like BabylonJS\n // Velocity smoothing for gradual start/stop\n private horizontalVelocity: pc.Vec3 = new pc.Vec3();\n private targetVelocity: pc.Vec3 = new pc.Vec3();\n // Collision meshes\n private collisionEntities: pc.Entity[] = [];\n private floorEntity: pc.Entity | null = null;\n // Event handlers (for cleanup)\n private keydownHandler: ((e: KeyboardEvent) => void) | null = null;\n private keyupHandler: ((e: KeyboardEvent) => void) | null = null;\n private mousemoveHandler: ((e: MouseEvent) => void) | null = null;\n private clickHandler: ((e: MouseEvent) => void) | null = null;\n private pointerlockchangeHandler: (() => void) | null = null;\n // Temporary vectors for calculations\n private tmpVec = new pc.Vec3();\n private tmpVec2 = new pc.Vec3();\n private forward = new pc.Vec3();\n private right = new pc.Vec3();\n constructor(camera: pc.Entity, app: pc.Application, config: CharacterControllerConfig = {}) {\n this.camera = camera;\n this.app = app;\n // Apply config\n if (config.moveSpeed !== undefined)\n this.moveSpeed = config.moveSpeed;\n if (config.sprintMultiplier !== undefined)\n this.sprintMultiplier = config.sprintMultiplier;\n if (config.lookSensitivity !== undefined)\n this.lookSensitivity = config.lookSensitivity;\n if (config.playerHeight !== undefined)\n this.playerHeight = config.playerHeight;\n if (config.gravity !== undefined)\n this.gravity = config.gravity;\n if (config.maxFallSpeed !== undefined)\n this.maxFallSpeed = config.maxFallSpeed;\n if (config.jumpVelocity !== undefined)\n this.jumpVelocity = config.jumpVelocity;\n if (config.collisionRadius !== undefined)\n this.collisionRadius = config.collisionRadius;\n if (config.stepHeight !== undefined)\n this.stepHeight = config.stepHeight;\n if (config.groundCheckDistance !== undefined)\n this.groundCheckDistance = config.groundCheckDistance;\n if (config.moveDamping !== undefined)\n this.moveDamping = config.moveDamping;\n // Initialize yaw/pitch from camera rotation\n const angles = this.camera.getEulerAngles();\n this.pitch = angles.x;\n this.yaw = angles.y;\n }\n /**\n * Create collision meshes from scene data\n */\n async createCollisionMeshes(collisionMeshesData: CollisionMeshData[]): Promise<void> {\n if (!collisionMeshesData || collisionMeshesData.length === 0)\n return;\n console.log('[CharacterController] Creating collision meshes:', collisionMeshesData.length);\n const loadPromises: Promise<void>[] = [];\n collisionMeshesData.forEach((data, index) => {\n // Handle custom mesh (GLB/GLTF files)\n if (data.meshType === 'custom' && data.customMeshUrl) {\n const promise = this.loadCustomCollisionMesh(data, index);\n loadPromises.push(promise);\n return;\n }\n let entity: pc.Entity | null = null;\n switch (data.meshType) {\n case 'cube':\n entity = new pc.Entity(`collision-cube-${index}`);\n entity.addComponent('render', {\n type: 'box'\n });\n break;\n case 'sphere':\n entity = new pc.Entity(`collision-sphere-${index}`);\n entity.addComponent('render', {\n type: 'sphere'\n });\n break;\n case 'floor':\n entity = new pc.Entity(`collision-floor-${index}`);\n entity.addComponent('render', {\n type: 'plane'\n });\n this.floorEntity = entity;\n break;\n case 'plane':\n default:\n entity = new pc.Entity(`collision-plane-${index}`);\n entity.addComponent('render', {\n type: 'plane'\n });\n break;\n }\n if (entity) {\n this.configureCollisionEntity(entity, data);\n }\n });\n // Wait for all custom meshes to load\n if (loadPromises.length > 0) {\n await Promise.all(loadPromises);\n }\n console.log('[CharacterController] Created', this.collisionEntities.length, 'collision entities');\n }\n /**\n * Load a custom collision mesh (GLB/GLTF)\n */\n private async loadCustomCollisionMesh(data: CollisionMeshData, index: number): Promise<void> {\n const url = data.customMeshUrl;\n if (!url) return;\n console.log('[CharacterController] Loading custom collision mesh:', url);\n try {\n // Determine asset type from URL\n const ext = url.split('?')[0].split('.').pop()?.toLowerCase() || 'glb';\n const assetType = (ext === 'gltf' || ext === 'glb') ? 'container' : 'model';\n const asset = new pc.Asset(`collision-custom-${index}`, assetType, { url });\n await new Promise<void>((resolve, reject) => {\n asset.ready(() => {\n try {\n const entity = new pc.Entity(`collision-custom-${index}`);\n if (assetType === 'container') {\n // GLB/GLTF container - instantiate the model\n const containerResource = asset.resource as pc.ContainerResource;\n if (containerResource && containerResource.instantiateRenderEntity) {\n const renderEntity = containerResource.instantiateRenderEntity();\n // Re-parent all children to our collision entity\n while (renderEntity.children.length > 0) {\n entity.addChild(renderEntity.children[0]);\n }\n renderEntity.destroy();\n }\n }\n else {\n // Regular model\n entity.addComponent('model', {\n asset: asset\n });\n }\n this.configureCollisionEntity(entity, data);\n // Store bounding box for custom mesh collision\n this.computeAndStoreBounds(entity);\n resolve();\n }\n catch (err) {\n console.error('[CharacterController] Error setting up custom mesh:', err);\n reject(err);\n }\n });\n asset.on('error', (err: unknown) => {\n console.error('[CharacterController] Error loading custom mesh:', url, err);\n reject(err);\n });\n this.app.assets.add(asset);\n this.app.assets.load(asset);\n });\n }\n catch (error) {\n console.error('[CharacterController] Failed to load custom collision mesh:', url, error);\n }\n }\n /**\n * Compute and store bounding box for a collision entity\n */\n private computeAndStoreBounds(entity: pc.Entity): void {\n // Traverse entity and its children to compute combined bounds\n const bounds = new pc.BoundingBox();\n let boundsInitialized = false;\n const traverse = (node: pc.Entity) => {\n if (node.render && node.render.meshInstances) {\n for (const mi of node.render.meshInstances) {\n if (mi.aabb) {\n if (!boundsInitialized) {\n bounds.copy(mi.aabb);\n boundsInitialized = true;\n }\n else {\n bounds.add(mi.aabb);\n }\n }\n }\n }\n for (const child of node.children) {\n if (child instanceof pc.Entity) {\n traverse(child);\n }\n }\n };\n traverse(entity);\n if (boundsInitialized) {\n (entity as CollisionEntity)._collisionBounds = bounds;\n }\n }\n /**\n * Configure a collision entity with transform and visibility\n */\n private configureCollisionEntity(entity: pc.Entity, data: CollisionMeshData): void {\n // Apply transform - convert from BabylonJS coordinates\n const pos = toXYZ(data.position, [0, 0, 0]);\n entity.setPosition(pos[0], pos[1], -pos[2]); // Negate Z\n const rot = toXYZ(data.rotation, [0, 0, 0]);\n entity.setEulerAngles(rot[0] * (180 / Math.PI), rot[1] * (180 / Math.PI), -rot[2] * (180 / Math.PI));\n const scale = toXYZ(data.scaling, [1, 1, 1]);\n entity.setLocalScale(scale[0], scale[1], scale[2]);\n // Make invisible but keep for collision\n this.setEntityVisibility(entity, false);\n // Store mesh type for collision logic\n (entity as CollisionEntity)._collisionMeshType = data.meshType;\n this.app.root.addChild(entity);\n this.collisionEntities.push(entity);\n }\n /**\n * Set visibility of entity and all children\n */\n private setEntityVisibility(entity: pc.Entity, visible: boolean): void {\n if (entity.render) {\n entity.render.enabled = visible;\n }\n for (const child of entity.children) {\n if (child instanceof pc.Entity) {\n this.setEntityVisibility(child, visible);\n }\n }\n }\n /**\n * Enable the character controller\n */\n enable(): void {\n if (this.enabled)\n return;\n this.enabled = true;\n // Sync yaw/pitch from current camera rotation\n const angles = this.camera.getEulerAngles();\n this.pitch = angles.x;\n this.yaw = angles.y;\n // Reset velocities\n this.velocity.set(0, 0, 0);\n this.horizontalVelocity.set(0, 0, 0);\n this.targetVelocity.set(0, 0, 0);\n // Setup input handlers\n this.setupInputHandlers();\n console.log('[CharacterController] Enabled');\n }\n /**\n * Disable the character controller\n */\n disable(): void {\n if (!this.enabled)\n return;\n this.enabled = false;\n // Remove input handlers\n this.removeInputHandlers();\n // Exit pointer lock\n if (document.pointerLockElement) {\n document.exitPointerLock();\n }\n console.log('[CharacterController] Disabled');\n }\n /**\n * Setup keyboard and mouse input handlers\n */\n private setupInputHandlers(): void {\n const canvas = this.app.graphicsDevice.canvas as HTMLCanvasElement;\n // Keyboard handlers\n this.keydownHandler = (e: KeyboardEvent) => {\n this.keys[e.code] = true;\n // Jump on space\n if (e.code === 'Space' && this.isGrounded) {\n this.velocity.y = this.jumpVelocity;\n this.isGrounded = false;\n }\n };\n this.keyupHandler = (e: KeyboardEvent) => {\n this.keys[e.code] = false;\n };\n // Mouse look handler\n this.mousemoveHandler = (e: MouseEvent) => {\n if (!this.mouseLocked)\n return;\n this.yaw -= e.movementX * this.lookSensitivity * 100;\n this.pitch -= e.movementY * this.lookSensitivity * 100;\n // Clamp pitch\n this.pitch = Math.max(-89, Math.min(89, this.pitch));\n };\n // Click to lock pointer\n this.clickHandler = () => {\n if (!this.mouseLocked) {\n canvas.requestPointerLock();\n }\n };\n // Pointer lock change handler\n this.pointerlockchangeHandler = () => {\n this.mouseLocked = document.pointerLockElement === canvas;\n };\n // Add event listeners\n document.addEventListener('keydown', this.keydownHandler);\n document.addEventListener('keyup', this.keyupHandler);\n document.addEventListener('mousemove', this.mousemoveHandler);\n canvas.addEventListener('click', this.clickHandler);\n document.addEventListener('pointerlockchange', this.pointerlockchangeHandler);\n }\n /**\n * Remove input handlers\n */\n private removeInputHandlers(): void {\n const canvas = this.app.graphicsDevice.canvas as HTMLCanvasElement;\n if (this.keydownHandler) {\n document.removeEventListener('keydown', this.keydownHandler);\n }\n if (this.keyupHandler) {\n document.removeEventListener('keyup', this.keyupHandler);\n }\n if (this.mousemoveHandler) {\n document.removeEventListener('mousemove', this.mousemoveHandler);\n }\n if (this.clickHandler) {\n canvas.removeEventListener('click', this.clickHandler);\n }\n if (this.pointerlockchangeHandler) {\n document.removeEventListener('pointerlockchange', this.pointerlockchangeHandler);\n }\n this.keys = {};\n }\n /**\n * Check if position collides with any collision mesh\n */\n private checkCollision(position: pc.Vec3, radius: number): boolean {\n // AABB collision check against collision entities\n for (const entity of this.collisionEntities) {\n const entityPos = entity.getPosition();\n const entityScale = entity.getLocalScale();\n const meshType = (entity as CollisionEntity)._collisionMeshType;\n if (meshType === 'floor' || meshType === 'plane') {\n // Skip floor for horizontal collision\n continue;\n }\n let halfWidth: number, halfHeight: number, halfDepth: number;\n let centerX: number, centerY: number, centerZ: number;\n // Check if entity has custom bounds (for imported GLB/GLTF meshes)\n const customBounds = (entity as CollisionEntity)._collisionBounds;\n if (customBounds) {\n // Custom bounds are stored as world-space AABB (mi.aabb already includes entity transform)\n // Use bounds center (mesh may not be centered at entity origin) and don't multiply by scale\n centerX = customBounds.center.x;\n centerY = customBounds.center.y;\n centerZ = customBounds.center.z;\n halfWidth = customBounds.halfExtents.x;\n halfHeight = customBounds.halfExtents.y;\n halfDepth = customBounds.halfExtents.z;\n }\n else {\n // Primitive shapes (box/sphere are 1x1x1 units, scale determines size)\n centerX = entityPos.x;\n centerY = entityPos.y;\n centerZ = entityPos.z;\n halfWidth = entityScale.x / 2;\n halfHeight = entityScale.y / 2;\n halfDepth = entityScale.z / 2;\n }\n const dx = Math.abs(position.x - centerX);\n const dy = Math.abs(position.y - centerY);\n const dz = Math.abs(position.z - centerZ);\n if (dx < halfWidth + radius &&\n dy < halfHeight + this.playerHeight / 2 &&\n dz < halfDepth + radius) {\n return true;\n }\n }\n return false;\n }\n /**\n * Check ground below player\n */\n private checkGround(position: pc.Vec3): number | null {\n // Check against floor entity if exists\n if (this.floorEntity) {\n const floorPos = this.floorEntity.getPosition();\n const floorScale = this.floorEntity.getLocalScale();\n // Check if we're above the floor plane\n const halfWidth = floorScale.x / 2 * 100; // Floors are typically large\n const halfDepth = floorScale.z / 2 * 100;\n if (Math.abs(position.x - floorPos.x) < halfWidth &&\n Math.abs(position.z - floorPos.z) < halfDepth) {\n return floorPos.y;\n }\n }\n // Check against other collision meshes for ground\n let highestGround: number | null = null;\n for (const entity of this.collisionEntities) {\n const entityPos = entity.getPosition();\n const entityScale = entity.getLocalScale();\n const meshType = (entity as CollisionEntity)._collisionMeshType;\n // Skip floor/plane (handled above) and sphere (not good for walking on)\n if (meshType === 'floor' || meshType === 'plane' || meshType === 'sphere') {\n continue;\n }\n let halfWidth: number, halfHeight: number, halfDepth: number;\n let centerX: number, centerY: number, centerZ: number;\n // Check if entity has custom bounds (for imported GLB/GLTF meshes)\n const customBounds = (entity as CollisionEntity)._collisionBounds;\n if (customBounds) {\n // World-space AABB - use bounds center and don't multiply by scale\n centerX = customBounds.center.x;\n centerY = customBounds.center.y;\n centerZ = customBounds.center.z;\n halfWidth = customBounds.halfExtents.x;\n halfHeight = customBounds.halfExtents.y;\n halfDepth = customBounds.halfExtents.z;\n }\n else {\n centerX = entityPos.x;\n centerY = entityPos.y;\n centerZ = entityPos.z;\n halfWidth = entityScale.x / 2;\n halfHeight = entityScale.y / 2;\n halfDepth = entityScale.z / 2;\n }\n const topY = centerY + halfHeight;\n // Check if we're above this mesh\n if (Math.abs(position.x - centerX) < halfWidth + this.collisionRadius &&\n Math.abs(position.z - centerZ) < halfDepth + this.collisionRadius &&\n position.y >= topY - this.stepHeight) {\n if (highestGround === null || topY > highestGround) {\n highestGround = topY;\n }\n }\n }\n return highestGround;\n }\n /**\n * Update the character controller - call this every frame\n */\n update(dt: number): void {\n if (!this.enabled)\n return;\n // Get movement input\n const moveX = (this.keys['KeyD'] || this.keys['ArrowRight'] ? 1 : 0) -\n (this.keys['KeyA'] || this.keys['ArrowLeft'] ? 1 : 0);\n const moveZ = (this.keys['KeyW'] || this.keys['ArrowUp'] ? 1 : 0) -\n (this.keys['KeyS'] || this.keys['ArrowDown'] ? 1 : 0);\n const isSprinting = this.keys['ShiftLeft'] || this.keys['ShiftRight'];\n // Calculate forward and right vectors (horizontal only)\n const yawRad = this.yaw * (Math.PI / 180);\n this.forward.set(-Math.sin(yawRad), 0, -Math.cos(yawRad));\n this.right.set(Math.cos(yawRad), 0, -Math.sin(yawRad));\n // Calculate target velocity based on input\n const speed = this.moveSpeed * (isSprinting ? this.sprintMultiplier : 1);\n this.targetVelocity.set(0, 0, 0);\n this.targetVelocity.add(this.tmpVec2.copy(this.forward).mulScalar(moveZ * speed));\n this.targetVelocity.add(this.tmpVec2.copy(this.right).mulScalar(moveX * speed));\n // Smooth the horizontal velocity toward target (creates ease-in/ease-out)\n const lerpFactor = damp(this.moveDamping, dt);\n this.horizontalVelocity.lerp(this.horizontalVelocity, this.targetVelocity, lerpFactor);\n // Apply gravity (vertical velocity is not smoothed for natural feel)\n if (!this.isGrounded) {\n this.velocity.y -= this.gravity * dt;\n this.velocity.y = Math.max(-this.maxFallSpeed, this.velocity.y);\n }\n // Get current position\n const currentPos = this.camera.getPosition().clone();\n const feetY = currentPos.y - this.playerHeight;\n // Calculate new position using smoothed horizontal velocity\n const newPos = new pc.Vec3();\n newPos.x = currentPos.x + this.horizontalVelocity.x * dt;\n newPos.y = currentPos.y + this.velocity.y * dt;\n newPos.z = currentPos.z + this.horizontalVelocity.z * dt;\n // Check horizontal collision (X axis)\n this.tmpVec2.set(newPos.x, currentPos.y, currentPos.z);\n if (this.checkCollision(this.tmpVec2, this.collisionRadius)) {\n newPos.x = currentPos.x;\n }\n // Check horizontal collision (Z axis)\n this.tmpVec2.set(newPos.x, currentPos.y, newPos.z);\n if (this.checkCollision(this.tmpVec2, this.collisionRadius)) {\n newPos.z = currentPos.z;\n }\n // Check ground\n const groundY = this.checkGround(newPos);\n const targetFeetY = newPos.y - this.playerHeight;\n if (groundY !== null && targetFeetY <= groundY + this.groundCheckDistance) {\n // On ground\n newPos.y = groundY + this.playerHeight;\n this.velocity.y = 0;\n this.isGrounded = true;\n }\n else if (groundY === null || targetFeetY > groundY + this.stepHeight) {\n // In air\n this.isGrounded = false;\n }\n // Apply new position\n this.camera.setPosition(newPos.x, newPos.y, newPos.z);\n // Apply rotation\n this.camera.setEulerAngles(this.pitch, this.yaw, 0);\n }\n /**\n * Clean up resources\n */\n destroy(): void {\n this.disable();\n // Remove collision entities\n for (const entity of this.collisionEntities) {\n entity.destroy();\n }\n this.collisionEntities = [];\n this.floorEntity = null;\n }\n /**\n * Get collision entities for use by other systems (e.g., CameraControls explore mode)\n */\n get collisionMeshEntities(): pc.Entity[] {\n return this.collisionEntities;\n }\n /**\n * Get whether the controller is grounded\n */\n get grounded(): boolean {\n return this.isGrounded;\n }\n /**\n * Get current velocity\n */\n getVelocity(): pc.Vec3 {\n return this.velocity.clone();\n }\n /**\n * Set position\n */\n setPosition(x: number, y: number, z: number): void {\n this.camera.setPosition(x, y, z);\n }\n /**\n * Set rotation (yaw and pitch in degrees)\n */\n setRotation(pitch: number, yaw: number): void {\n this.pitch = pitch;\n this.yaw = yaw;\n this.camera.setEulerAngles(pitch, yaw, 0);\n }\n}\n","/**\n * GsplatRevealRadial - Radial reveal effect for gaussian splats\n *\n * Creates two waves emanating from a center point:\n * 1. Dot wave: Small colored dots appear progressively\n * 2. Lift wave: Particles lift up, get highlighted, then settle to original state\n *\n * Ported from PlayCanvas engine examples\n */\nimport * as pc from 'playcanvas';\nconst shaderGLSL = /* glsl */ `\nuniform float uTime;\nuniform vec3 uCenter;\nuniform float uSpeed;\nuniform float uAcceleration;\nuniform float uDelay;\nuniform vec3 uDotTint;\nuniform vec3 uWaveTint;\nuniform float uOscillationIntensity;\nuniform float uEndRadius;\n\n// Shared globals (initialized once per vertex)\nfloat g_dist;\nfloat g_dotWavePos;\nfloat g_liftTime;\nfloat g_liftWavePos;\n\nvoid initShared(vec3 center) {\n g_dist = length(center - uCenter);\n g_dotWavePos = uSpeed * uTime + 0.5 * uAcceleration * uTime * uTime;\n g_liftTime = max(0.0, uTime - uDelay);\n g_liftWavePos = uSpeed * g_liftTime + 0.5 * uAcceleration * g_liftTime * g_liftTime;\n}\n\n// Hash function for per-splat randomization\nfloat hash(vec3 p) {\n return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453);\n}\n\nvoid modifyCenter(inout vec3 center) {\n initShared(center);\n\n // Early exit optimization\n if (g_dist > uEndRadius) return;\n\n // Only apply oscillation if lift wave hasn't fully passed\n bool wavesActive = g_liftTime <= 0.0 || g_dist > g_liftWavePos - 1.5;\n if (wavesActive) {\n // Apply oscillation with per-splat phase offset\n float phase = hash(center) * 6.28318;\n center.y += sin(uTime * 3.0 + phase) * uOscillationIntensity * 0.25;\n }\n\n // Apply lift effect near the wave edge\n float distToLiftWave = abs(g_dist - g_liftWavePos);\n if (distToLiftWave < 1.0 && g_liftTime > 0.0) {\n // Create a smooth lift curve (peaks at wave edge)\n // Lift is 0.9x the oscillation intensity (30% of original 3x)\n float liftAmount = (1.0 - distToLiftWave) * sin(distToLiftWave * 3.14159);\n center.y += liftAmount * uOscillationIntensity * 0.9;\n }\n}\n\nvoid modifyCovariance(vec3 originalCenter, vec3 modifiedCenter, inout vec3 covA, inout vec3 covB) {\n // Early exit for distant splats - hide them\n if (g_dist > uEndRadius) {\n gsplatMakeRound(covA, covB, 0.0);\n return;\n }\n\n // Determine scale and phase\n float scale;\n bool isLiftWave = g_liftTime > 0.0 && g_liftWavePos > g_dist;\n\n if (isLiftWave) {\n // Lift wave: transition from dots to full size\n scale = (g_liftWavePos >= g_dist + 2.0) ? 1.0 : mix(0.1, 1.0, (g_liftWavePos - g_dist) * 0.5);\n } else if (g_dist > g_dotWavePos + 1.0) {\n // Before dot wave: invisible\n gsplatMakeRound(covA, covB, 0.0);\n return;\n } else if (g_dist > g_dotWavePos - 1.0) {\n // Dot wave front: scale from 0 to 0.1 with 2x peak at center\n float distToWave = abs(g_dist - g_dotWavePos);\n scale = (distToWave < 0.5)\n ? mix(0.1, 0.2, 1.0 - distToWave * 2.0)\n : mix(0.0, 0.1, smoothstep(g_dotWavePos + 1.0, g_dotWavePos - 1.0, g_dist));\n } else {\n // After dot wave, before lift: small dots\n scale = 0.1;\n }\n\n // Apply scale to covariance\n if (scale >= 1.0) {\n // Fully revealed: original shape and size (no-op)\n return;\n } else if (isLiftWave) {\n // Lift wave: lerp from round dots to original shape\n float t = (scale - 0.1) * 1.111111; // normalize [0.1, 1.0] to [0, 1]\n float dotSize = scale * 0.05;\n float originalSize = gsplatExtractSize(covA, covB);\n float finalSize = mix(dotSize, originalSize, t);\n\n // Lerp between round and scaled original\n vec3 origCovA = covA * (scale * scale);\n vec3 origCovB = covB * (scale * scale);\n gsplatMakeRound(covA, covB, finalSize);\n covA = mix(covA, origCovA, t);\n covB = mix(covB, origCovB, t);\n } else {\n // Dot phase: round with absolute size, but don't make small splats larger\n float originalSize = gsplatExtractSize(covA, covB);\n gsplatMakeRound(covA, covB, min(scale * 0.05, originalSize));\n }\n}\n\nvoid modifyColor(vec3 center, inout vec4 color) {\n // Use shared globals\n if (g_dist > uEndRadius) return;\n\n // Lift wave tint takes priority (active during lift)\n if (g_liftTime > 0.0 && g_dist >= g_liftWavePos - 1.5 && g_dist <= g_liftWavePos + 0.5) {\n float distToLift = abs(g_dist - g_liftWavePos);\n float liftIntensity = smoothstep(1.5, 0.0, distToLift);\n color.rgb += uWaveTint * liftIntensity;\n }\n // Dot wave tint (active in dot phase, but not where lift wave is active)\n else if (g_dist <= g_dotWavePos && (g_liftTime <= 0.0 || g_dist > g_liftWavePos + 0.5)) {\n float distToDot = abs(g_dist - g_dotWavePos);\n float dotIntensity = smoothstep(1.0, 0.0, distToDot);\n color.rgb += uDotTint * dotIntensity;\n }\n}\n`;\nconst shaderWGSL = /* wgsl */ `\nuniform uTime: f32;\nuniform uCenter: vec3f;\nuniform uSpeed: f32;\nuniform uAcceleration: f32;\nuniform uDelay: f32;\nuniform uDotTint: vec3f;\nuniform uWaveTint: vec3f;\nuniform uOscillationIntensity: f32;\nuniform uEndRadius: f32;\n\n// Shared globals (initialized once per vertex)\nvar<private> g_dist: f32;\nvar<private> g_dotWavePos: f32;\nvar<private> g_liftTime: f32;\nvar<private> g_liftWavePos: f32;\n\nfn initShared(center: vec3f) {\n g_dist = length(center - uniform.uCenter);\n g_dotWavePos = uniform.uSpeed * uniform.uTime + 0.5 * uniform.uAcceleration * uniform.uTime * uniform.uTime;\n g_liftTime = max(0.0, uniform.uTime - uniform.uDelay);\n g_liftWavePos = uniform.uSpeed * g_liftTime + 0.5 * uniform.uAcceleration * g_liftTime * g_liftTime;\n}\n\n// Hash function for per-splat randomization\nfn hash(p: vec3f) -> f32 {\n return fract(sin(dot(p, vec3f(127.1, 311.7, 74.7))) * 43758.5453);\n}\n\nfn modifyCenter(center: ptr<function, vec3f>) {\n initShared(*center);\n\n // Early exit optimization\n if (g_dist > uniform.uEndRadius) {\n return;\n }\n\n // Only apply oscillation if lift wave hasn't fully passed\n let wavesActive = g_liftTime <= 0.0 || g_dist > g_liftWavePos - 1.5;\n if (wavesActive) {\n // Apply oscillation with per-splat phase offset\n let phase = hash(*center) * 6.28318;\n (*center).y += sin(uniform.uTime * 3.0 + phase) * uniform.uOscillationIntensity * 0.25;\n }\n\n // Apply lift effect near the wave edge\n let distToLiftWave = abs(g_dist - g_liftWavePos);\n if (distToLiftWave < 1.0 && g_liftTime > 0.0) {\n // Create a smooth lift curve (peaks at wave edge)\n // Lift is 0.9x the oscillation intensity (30% of original 3x)\n let liftAmount = (1.0 - distToLiftWave) * sin(distToLiftWave * 3.14159);\n (*center).y += liftAmount * uniform.uOscillationIntensity * 0.9;\n }\n}\n\nfn modifyCovariance(originalCenter: vec3f, modifiedCenter: vec3f, covA: ptr<function, vec3f>, covB: ptr<function, vec3f>) {\n // Early exit for distant splats - hide them\n if (g_dist > uniform.uEndRadius) {\n gsplatMakeRound(covA, covB, 0.0);\n return;\n }\n\n // Determine scale and phase\n var scale: f32;\n let isLiftWave = g_liftTime > 0.0 && g_liftWavePos > g_dist;\n\n if (isLiftWave) {\n // Lift wave: transition from dots to full size\n scale = select(mix(0.1, 1.0, (g_liftWavePos - g_dist) * 0.5), 1.0, g_liftWavePos >= g_dist + 2.0);\n } else if (g_dist > g_dotWavePos + 1.0) {\n // Before dot wave: invisible\n gsplatMakeRound(covA, covB, 0.0);\n return;\n } else if (g_dist > g_dotWavePos - 1.0) {\n // Dot wave front: scale from 0 to 0.1 with 2x peak at center\n let distToWave = abs(g_dist - g_dotWavePos);\n scale = select(\n mix(0.0, 0.1, smoothstep(g_dotWavePos + 1.0, g_dotWavePos - 1.0, g_dist)),\n mix(0.1, 0.2, 1.0 - distToWave * 2.0),\n distToWave < 0.5\n );\n } else {\n // After dot wave, before lift: small dots\n scale = 0.1;\n }\n\n // Apply scale to covariance\n if (scale >= 1.0) {\n // Fully revealed: original shape and size (no-op)\n return;\n } else if (isLiftWave) {\n // Lift wave: lerp from round dots to original shape\n let t = (scale - 0.1) * 1.111111; // normalize [0.1, 1.0] to [0, 1]\n let dotSize = scale * 0.05;\n let originalSize = gsplatExtractSize(*covA, *covB);\n let finalSize = mix(dotSize, originalSize, t);\n\n // Lerp between round and scaled original\n let origCovA = *covA * (scale * scale);\n let origCovB = *covB * (scale * scale);\n gsplatMakeRound(covA, covB, finalSize);\n *covA = mix(*covA, origCovA, t);\n *covB = mix(*covB, origCovB, t);\n } else {\n // Dot phase: round with absolute size, but don't make small splats larger\n let originalSize = gsplatExtractSize(*covA, *covB);\n gsplatMakeRound(covA, covB, min(scale * 0.05, originalSize));\n }\n}\n\nfn modifyColor(center: vec3f, color: ptr<function, vec4f>) {\n // Use shared globals\n if (g_dist > uniform.uEndRadius) {\n return;\n }\n\n // Lift wave tint takes priority (active during lift)\n if (g_liftTime > 0.0 && g_dist >= g_liftWavePos - 1.5 && g_dist <= g_liftWavePos + 0.5) {\n let distToLift = abs(g_dist - g_liftWavePos);\n let liftIntensity = smoothstep(1.5, 0.0, distToLift);\n (*color) = vec4f((*color).rgb + uniform.uWaveTint * liftIntensity, (*color).a);\n }\n // Dot wave tint (active in dot phase, but not where lift wave is active)\n else if (g_dist <= g_dotWavePos && (g_liftTime <= 0.0 || g_dist > g_liftWavePos + 0.5)) {\n let distToDot = abs(g_dist - g_dotWavePos);\n let dotIntensity = smoothstep(1.0, 0.0, distToDot);\n (*color) = vec4f((*color).rgb + uniform.uDotTint * dotIntensity, (*color).a);\n }\n}\n`;\n// Cache for the script class\nlet _GsplatRevealRadialClass: typeof pc.ScriptType | null = null;\ntype ScriptTypeWithPrototype<T extends pc.ScriptType> = typeof pc.ScriptType & {\n prototype: T;\n};\ntype ShaderChunkMaterial = pc.Material & {\n gsplat?: boolean;\n setShaderChunk?: (name: string, chunk: string) => void;\n chunks?: Record<string, string>;\n options?: unknown;\n shader?: unknown;\n};\ntype MaterialCollection = {\n size?: number;\n forEach: (callback: (material: pc.Material) => void) => void;\n};\ntype GSplatInstance = {\n constructor?: {\n name?: string;\n };\n materials?: MaterialCollection | pc.Material[];\n _materials?: MaterialCollection | pc.Material[];\n material?: pc.Material;\n _material?: pc.Material;\n on?: (event: string, callback: (material: pc.Material) => void) => void;\n off?: (event: string, callback: (material: pc.Material) => void) => void;\n};\ntype GSplatComponentWithRuntime = Omit<pc.GSplatComponent, '_instance'> & {\n unified?: boolean;\n instance?: GSplatInstance;\n _instance?: GSplatInstance;\n layers?: number[];\n asset?: unknown;\n};\ntype GSplatSystemWithRuntime = {\n on?: (event: string, callback: (material: pc.Material, camera: unknown, layer: unknown) => void) => void;\n off?: (event: string, callback: (material: pc.Material, camera: unknown, layer: unknown) => void) => void;\n getGSplatMaterial?: (camera: unknown, layer: unknown) => pc.Material | null | undefined;\n};\ntype RevealRuntimeEntity = Omit<pc.Entity, 'gsplat'> & {\n gsplat?: GSplatComponentWithRuntime;\n};\ninterface GsplatRevealRadialScript extends pc.ScriptType {\n effectTime: number;\n _materialsApplied: Set<pc.Material> | null;\n _shadersApplied: boolean;\n _retryCount: number;\n _maxRetries: number;\n _materialCreatedHandler: ((material: pc.Material) => void) | null;\n _systemMaterialHandler: ((material: pc.Material, camera: unknown, layer: unknown) => void) | null;\n _centerArray: [\n number,\n number,\n number\n ];\n _dotTintArray: [\n number,\n number,\n number\n ];\n _waveTintArray: [\n number,\n number,\n number\n ];\n center: pc.Vec3 | null;\n speed: number;\n acceleration: number;\n delay: number;\n dotTint: pc.Color | null;\n waveTint: pc.Color | null;\n oscillationIntensity: number;\n endRadius: number;\n getShaderGLSL: () => string;\n getShaderWGSL: () => string;\n _applyShaders: () => void;\n _applyToInstance: (instance: GSplatInstance) => void;\n _applyShaderToMaterial: (material: pc.Material) => void;\n _removeShaders: () => void;\n _updateUniforms: () => void;\n _setUniform: (name: string, value: number | number[]) => void;\n _getCompletionTime: () => number;\n _isEffectComplete: () => boolean;\n}\n/**\n * Gets or creates the GsplatRevealRadial script class.\n * Must be called after PlayCanvas app is initialized.\n */\nexport function getGsplatRevealRadialClass(): typeof pc.ScriptType {\n console.log('[RevealEffect] getGsplatRevealRadialClass called, cached:', !!_GsplatRevealRadialClass);\n if (_GsplatRevealRadialClass) {\n return _GsplatRevealRadialClass;\n }\n console.log('[RevealEffect] Creating new script class via pc.createScript');\n const GsplatRevealRadial = pc.createScript('gsplatRevealRadial') as ScriptTypeWithPrototype<GsplatRevealRadialScript>;\n console.log('[RevealEffect] Script class created:', GsplatRevealRadial);\n Object.assign(GsplatRevealRadial.prototype, {\n // Properties\n effectTime: 0,\n _materialsApplied: null as Set<pc.Material> | null,\n _shadersApplied: false,\n _retryCount: 0,\n _maxRetries: 100,\n _materialCreatedHandler: null as ((material: pc.Material) => void) | null,\n _systemMaterialHandler: null as ((material: pc.Material, camera: unknown, layer: unknown) => void) | null,\n // Reusable arrays for uniform updates\n _centerArray: [0, 0, 0],\n _dotTintArray: [0, 0, 0],\n _waveTintArray: [0, 0, 0],\n // Effect properties\n center: null as pc.Vec3 | null,\n speed: 5,\n acceleration: 0,\n delay: 2,\n dotTint: null as pc.Color | null,\n waveTint: null as pc.Color | null,\n oscillationIntensity: 0.2,\n endRadius: 25,\n initialize(this: GsplatRevealRadialScript) {\n console.log('[RevealEffect] initialize() called');\n this.effectTime = 0;\n this._materialsApplied = new Set();\n this._shadersApplied = false;\n this._retryCount = 0;\n this._centerArray = [0, 0, 0];\n this._dotTintArray = [0, 0, 0];\n this._waveTintArray = [0, 0, 0];\n // Initialize Vec3 and Color if not set\n if (!this.center) {\n this.center = new pc.Vec3(0, 0, 0);\n }\n if (!this.dotTint) {\n this.dotTint = new pc.Color(0, 1, 1); // Cyan\n }\n if (!this.waveTint) {\n this.waveTint = new pc.Color(1, 0.5, 0); // Orange\n }\n this.on('enable', () => {\n console.log('[RevealEffect] enabled event fired');\n this.effectTime = 0;\n this._applyShaders();\n });\n this.on('disable', () => {\n console.log('[RevealEffect] disabled event fired');\n this._removeShaders();\n });\n if (this.enabled) {\n console.log('[RevealEffect] Starting enabled, applying shaders');\n this._applyShaders();\n }\n else {\n console.log('[RevealEffect] Starting disabled, waiting for enable');\n }\n },\n update(this: GsplatRevealRadialScript, dt: number) {\n if (!this._shadersApplied && this._retryCount < this._maxRetries) {\n this._retryCount++;\n if (this._retryCount % 20 === 0) {\n console.log(`[RevealEffect] Retry ${this._retryCount}/${this._maxRetries} to apply shaders`);\n }\n this._applyShaders();\n }\n this.effectTime += dt;\n // Log every second\n if (Math.floor(this.effectTime) !== Math.floor(this.effectTime - dt)) {\n console.log(`[RevealEffect] effectTime: ${this.effectTime.toFixed(2)}s, shadersApplied: ${this._shadersApplied}, materialsCount: ${this._materialsApplied?.size || 0}`);\n }\n if (this._isEffectComplete()) {\n console.log('[RevealEffect] Effect complete, disabling');\n this.enabled = false;\n return;\n }\n this._updateUniforms();\n },\n _updateUniforms(this: GsplatRevealRadialScript) {\n this._setUniform('uTime', this.effectTime);\n this._centerArray[0] = this.center!.x;\n this._centerArray[1] = this.center!.y;\n this._centerArray[2] = this.center!.z;\n this._setUniform('uCenter', this._centerArray);\n this._setUniform('uSpeed', this.speed);\n this._setUniform('uAcceleration', this.acceleration);\n this._setUniform('uDelay', this.delay);\n this._dotTintArray[0] = this.dotTint!.r;\n this._dotTintArray[1] = this.dotTint!.g;\n this._dotTintArray[2] = this.dotTint!.b;\n this._setUniform('uDotTint', this._dotTintArray);\n this._waveTintArray[0] = this.waveTint!.r;\n this._waveTintArray[1] = this.waveTint!.g;\n this._waveTintArray[2] = this.waveTint!.b;\n this._setUniform('uWaveTint', this._waveTintArray);\n this._setUniform('uOscillationIntensity', this.oscillationIntensity);\n this._setUniform('uEndRadius', this.endRadius);\n },\n _getCompletionTime(this: GsplatRevealRadialScript): number {\n const liftStartTime = this.delay;\n if (this.acceleration === 0) {\n return liftStartTime + (this.endRadius / this.speed);\n }\n const discriminant = this.speed * this.speed + 2 * this.acceleration * this.endRadius;\n if (discriminant < 0) {\n return Infinity;\n }\n const t = (-this.speed + Math.sqrt(discriminant)) / this.acceleration;\n return liftStartTime + t;\n },\n _isEffectComplete(this: GsplatRevealRadialScript): boolean {\n return this.effectTime >= this._getCompletionTime();\n },\n getShaderGLSL(): string {\n return shaderGLSL;\n },\n getShaderWGSL(): string {\n return shaderWGSL;\n },\n // Shader application methods\n _applyShaders(this: GsplatRevealRadialScript) {\n const gsplatComponent = this.entity.gsplat;\n if (!gsplatComponent) {\n console.log('[RevealEffect] _applyShaders: No gsplat component found on entity');\n return;\n }\n // Check if unified mode is enabled\n const gsplatRuntime = gsplatComponent as unknown as GSplatComponentWithRuntime;\n const isUnified = gsplatRuntime.unified === true;\n const app = this.app;\n if (isUnified) {\n console.log('[RevealEffect] Unified mode detected, using GSplatComponentSystem');\n // In unified mode, materials are accessed via GSplatComponentSystem\n const gsplatSystem = app?.systems?.gsplat as GSplatSystemWithRuntime | undefined;\n if (gsplatSystem) {\n // Subscribe to material:created event if not already subscribed\n if (!this._systemMaterialHandler) {\n this._systemMaterialHandler = (material: pc.Material, camera: unknown, layer: unknown) => {\n console.log('[RevealEffect] material:created event from GSplatComponentSystem');\n this._applyShaderToMaterial(material);\n this._shadersApplied = true;\n };\n gsplatSystem.on?.('material:created', this._systemMaterialHandler);\n console.log('[RevealEffect] Subscribed to GSplatComponentSystem material:created event');\n }\n // Try to get existing materials for all camera/layer combinations\n const cameras = app.root?.findComponents('camera') || [];\n const layers = gsplatRuntime.layers || [0]; // Default to layer 0 if not specified\n for (const cameraComp of cameras) {\n for (const layerId of layers) {\n const layer = app.scene?.layers?.getLayerById(layerId);\n if (layer) {\n const material = gsplatSystem.getGSplatMaterial?.((cameraComp as unknown as { camera: unknown }).camera, layer);\n if (material) {\n console.log('[RevealEffect] Found unified material via getGSplatMaterial:', material);\n this._applyShaderToMaterial(material);\n this._shadersApplied = true;\n }\n }\n }\n }\n }\n return;\n }\n // Non-unified mode: Check both instance and _instance\n const instance = gsplatRuntime.instance || gsplatRuntime._instance;\n if (instance) {\n console.log('[RevealEffect] Instance found via component:', instance);\n this._applyToInstance(instance);\n return;\n }\n // Try to find gsplat materials through the scene's mesh instances\n if (app && app.scene) {\n // Search through layers for gsplat mesh instances\n const layers = app.scene.layers?.layerList || [];\n for (const layer of layers) {\n if (!layer.meshInstances)\n continue;\n for (const mi of layer.meshInstances) {\n // Check if this is a gsplat mesh instance\n const miRuntime = mi as pc.MeshInstance & { _gsplatInstance?: GSplatInstance };\n if (mi.gsplatInstance || miRuntime._gsplatInstance) {\n const gsplatInst = (mi.gsplatInstance || miRuntime._gsplatInstance) as GSplatInstance;\n console.log('[RevealEffect] Found gsplat instance via mesh instance:', gsplatInst);\n this._applyToInstance(gsplatInst);\n return;\n }\n // Check material for gsplat characteristics\n const material = mi.material;\n if (material && (material as ShaderChunkMaterial).gsplat) {\n console.log('[RevealEffect] Found gsplat material via mesh instance:', material);\n this._applyShaderToMaterial(material);\n this._shadersApplied = true;\n return;\n }\n }\n }\n // Also check root entities for gsplat components\n const checkEntity = (entity: pc.Entity): boolean => {\n const runtimeEntity = entity as RevealRuntimeEntity;\n if (runtimeEntity.gsplat) {\n const inst = runtimeEntity.gsplat.instance || runtimeEntity.gsplat._instance;\n if (inst) {\n console.log('[RevealEffect] Found gsplat instance via entity search:', inst);\n this._applyToInstance(inst);\n return true;\n }\n }\n for (const child of (entity.children || []) as pc.Entity[]) {\n if (checkEntity(child))\n return true;\n }\n return false;\n };\n if (app.root) {\n checkEntity(app.root);\n }\n }\n // Log state for debugging\n if (this._retryCount % 50 === 0) {\n console.log('[RevealEffect] Still searching for gsplat materials...');\n console.log('[RevealEffect] gsplatComponent.unified:', gsplatRuntime.unified);\n console.log('[RevealEffect] gsplatComponent.asset:', gsplatComponent.asset);\n }\n },\n _applyToInstance(this: GsplatRevealRadialScript, instance: GSplatInstance) {\n if (this._shadersApplied) {\n return;\n }\n console.log('[RevealEffect] Applying shaders to instance');\n console.log('[RevealEffect] Instance type:', instance.constructor?.name);\n console.log('[RevealEffect] Instance keys:', Object.keys(instance));\n // Try to find materials on the instance\n const materials = instance.materials || instance._materials;\n console.log('[RevealEffect] Instance materials:', materials);\n if (materials) {\n const materialsAsMC = materials as MaterialCollection;\n if (materials instanceof Map || (materialsAsMC.forEach && materialsAsMC.size !== undefined)) {\n console.log('[RevealEffect] Materials is a Map/Set with size:', materialsAsMC.size);\n if (materialsAsMC.size! > 0) {\n materialsAsMC.forEach((material: pc.Material) => {\n this._applyShaderToMaterial(material);\n });\n this._shadersApplied = true;\n console.log('[RevealEffect] SUCCESS: Shaders applied to', materialsAsMC.size, 'materials');\n }\n }\n else if (Array.isArray(materials)) {\n console.log('[RevealEffect] Materials is array with length:', materials.length);\n materials.forEach((material: pc.Material) => {\n this._applyShaderToMaterial(material);\n });\n this._shadersApplied = true;\n }\n }\n // Also try material property directly\n if (!this._shadersApplied) {\n const material = instance.material || instance._material;\n if (material) {\n console.log('[RevealEffect] Found single material on instance');\n this._applyShaderToMaterial(material);\n this._shadersApplied = true;\n }\n }\n // Subscribe to material:created for future materials\n if (instance.on && !this._materialCreatedHandler) {\n this._materialCreatedHandler = (material: pc.Material) => {\n console.log('[RevealEffect] material:created event received');\n this._applyShaderToMaterial(material);\n this._shadersApplied = true;\n };\n instance.on('material:created', this._materialCreatedHandler);\n console.log('[RevealEffect] Subscribed to material:created event');\n }\n },\n _applyShaderToMaterial(this: GsplatRevealRadialScript, material: pc.Material) {\n if (this._materialsApplied?.has(material)) {\n console.log('[RevealEffect] Material already has shader applied, skipping');\n return;\n }\n console.log('[RevealEffect] Applying shader to material:', material);\n console.log('[RevealEffect] Material constructor:', material.constructor?.name);\n console.log('[RevealEffect] Material keys:', Object.keys(material));\n // Log all methods on the material\n const methods: string[] = [];\n let obj: object | null = material;\n while (obj && obj !== Object.prototype) {\n const names = Object.getOwnPropertyNames(obj);\n for (const name of names) {\n try {\n const candidate = obj as Record<string, unknown>;\n if (typeof candidate[name] === 'function' && !methods.includes(name)) {\n methods.push(name);\n }\n }\n catch (_error) { }\n }\n obj = Object.getPrototypeOf(obj);\n }\n console.log('[RevealEffect] Material methods:', methods.filter(m => !m.startsWith('_')).join(', '));\n const glsl = this.getShaderGLSL();\n const wgsl = this.getShaderWGSL();\n // Try setShaderChunk first (newer API)\n const materialWithShaders = material as ShaderChunkMaterial;\n const hasSetShaderChunk = typeof materialWithShaders.setShaderChunk === 'function';\n console.log('[RevealEffect] material.setShaderChunk exists:', hasSetShaderChunk);\n if (hasSetShaderChunk) {\n if (glsl) {\n materialWithShaders.setShaderChunk?.('gsplatEffectGLSL', glsl);\n console.log('[RevealEffect] GLSL shader chunk set via setShaderChunk');\n }\n if (wgsl) {\n materialWithShaders.setShaderChunk?.('gsplatEffectWGSL', wgsl);\n console.log('[RevealEffect] WGSL shader chunk set via setShaderChunk');\n }\n }\n else {\n // Try alternative: chunks property (older API)\n if (materialWithShaders.chunks) {\n console.log('[RevealEffect] Material has chunks property');\n materialWithShaders.chunks.gsplatEffectGLSL = glsl;\n materialWithShaders.chunks.gsplatEffectWGSL = wgsl;\n console.log('[RevealEffect] Set chunks directly');\n }\n // Try: customFragmentShader or options\n if (materialWithShaders.options) {\n console.log('[RevealEffect] Material has options:', materialWithShaders.options);\n }\n // Try: shader property\n if (materialWithShaders.shader) {\n console.log('[RevealEffect] Material has shader:', materialWithShaders.shader);\n }\n // Try: setParameter for uniforms (this should work)\n if (typeof material.setParameter === 'function') {\n console.log('[RevealEffect] material.setParameter exists - will use for uniforms');\n }\n }\n material.update?.();\n this._materialsApplied?.add(material);\n console.log('[RevealEffect] Material added to applied set, total:', this._materialsApplied?.size);\n },\n _removeShaders(this: GsplatRevealRadialScript) {\n if (this._materialsApplied) {\n this._materialsApplied.forEach((material: pc.Material) => {\n const materialWithShaders = material as ShaderChunkMaterial;\n materialWithShaders.setShaderChunk?.('gsplatEffectGLSL', '');\n materialWithShaders.setShaderChunk?.('gsplatEffectWGSL', '');\n material.update?.();\n });\n this._materialsApplied.clear();\n }\n this._shadersApplied = false;\n // Clean up instance-level handler (non-unified mode)\n if (this._materialCreatedHandler) {\n const gsplatComponent = this.entity.gsplat;\n const gsplatInstance = (gsplatComponent as GSplatComponentWithRuntime | undefined)?.instance;\n if (gsplatInstance?.off) {\n gsplatInstance.off('material:created', this._materialCreatedHandler);\n }\n this._materialCreatedHandler = null;\n }\n // Clean up system-level handler (unified mode)\n if (this._systemMaterialHandler) {\n const gsplatSystem = this.app?.systems?.gsplat;\n if (gsplatSystem?.off) {\n gsplatSystem.off('material:created', this._systemMaterialHandler);\n }\n this._systemMaterialHandler = null;\n }\n },\n _setUniform(this: GsplatRevealRadialScript, name: string, value: number | number[]) {\n if (this._materialsApplied) {\n this._materialsApplied.forEach((material: pc.Material) => {\n material.setParameter?.(name, value);\n });\n }\n },\n destroy(this: GsplatRevealRadialScript) {\n this._removeShaders();\n }\n });\n _GsplatRevealRadialClass = GsplatRevealRadial;\n return GsplatRevealRadial;\n}\n// For backwards compatibility\nexport const GsplatRevealRadial = {\n get class() {\n return getGsplatRevealRadialClass();\n }\n};\n","/**\n * Reveal Effect Presets\n *\n * Pre-configured settings for the radial reveal effect.\n * Users can choose from fast, medium, or slow presets.\n */\n\nexport interface RevealPresetConfig {\n /** Base wave speed in units/second */\n speed: number;\n /** Speed increase over time */\n acceleration: number;\n /** Time offset before lift wave starts (seconds) */\n delay: number;\n /** Position oscillation strength */\n oscillationIntensity: number;\n /** Additive color for initial dots */\n dotTint: { r: number; g: number; b: number };\n /** Additive color for lift wave highlight */\n waveTint: { r: number; g: number; b: number };\n /** Distance at which to disable effect for performance */\n endRadius: number;\n}\n\n/**\n * Reveal effect presets\n */\nexport const REVEAL_PRESETS: Record<string, RevealPresetConfig> = {\n /**\n * Fast preset - Quick reveal for shorter loading experiences\n * Duration: ~3-4 seconds for a 50 unit radius scene\n */\n fast: {\n speed: 10,\n acceleration: 2,\n delay: 0.5,\n oscillationIntensity: 0.1,\n dotTint: { r: 0, g: 1, b: 1 }, // Cyan\n waveTint: { r: 1, g: 0.5, b: 0 }, // Orange\n endRadius: 50\n },\n\n /**\n * Medium preset - Balanced reveal for typical scenes\n * Duration: ~5-7 seconds for a 50 unit radius scene\n */\n medium: {\n speed: 5,\n acceleration: 0,\n delay: 2,\n oscillationIntensity: 0.2,\n dotTint: { r: 0, g: 1, b: 1 }, // Cyan\n waveTint: { r: 1, g: 0.5, b: 0 }, // Orange\n endRadius: 50\n },\n\n /**\n * Slow preset - Dramatic reveal for showcase/demo scenes\n * Duration: ~12-15 seconds for a 50 unit radius scene\n */\n slow: {\n speed: 3,\n acceleration: 0,\n delay: 3,\n oscillationIntensity: 0.25,\n dotTint: { r: 0, g: 1, b: 1 }, // Cyan\n waveTint: { r: 1, g: 0.5, b: 0 }, // Orange\n endRadius: 50\n }\n};\n\n/**\n * Type for reveal effect preset names\n */\nexport type RevealPreset = 'fast' | 'medium' | 'slow' | 'none';\n\n/**\n * Get preset configuration by name\n * Returns undefined for 'none' or invalid presets\n */\nexport function getRevealPreset(preset: RevealPreset): RevealPresetConfig | undefined {\n if (preset === 'none') {\n return undefined;\n }\n return REVEAL_PRESETS[preset];\n}\n","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.loop = exports.conditional = exports.parse = void 0;\n\nvar parse = function parse(stream, schema) {\n var result = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};\n var parent = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : result;\n\n if (Array.isArray(schema)) {\n schema.forEach(function (partSchema) {\n return parse(stream, partSchema, result, parent);\n });\n } else if (typeof schema === 'function') {\n schema(stream, result, parent, parse);\n } else {\n var key = Object.keys(schema)[0];\n\n if (Array.isArray(schema[key])) {\n parent[key] = {};\n parse(stream, schema[key], result, parent[key]);\n } else {\n parent[key] = schema[key](stream, result, parent, parse);\n }\n }\n\n return result;\n};\n\nexports.parse = parse;\n\nvar conditional = function conditional(schema, conditionFunc) {\n return function (stream, result, parent, parse) {\n if (conditionFunc(stream, result, parent)) {\n parse(stream, schema, result, parent);\n }\n };\n};\n\nexports.conditional = conditional;\n\nvar loop = function loop(schema, continueFunc) {\n return function (stream, result, parent, parse) {\n var arr = [];\n var lastStreamPos = stream.pos;\n\n while (continueFunc(stream, result, parent)) {\n var newParent = {};\n parse(stream, schema, result, newParent); // cases when whole file is parsed but no termination is there and stream position is not getting updated as well\n // it falls into infinite recursion, null check to avoid the same\n\n if (stream.pos === lastStreamPos) {\n break;\n }\n\n lastStreamPos = stream.pos;\n arr.push(newParent);\n }\n\n return arr;\n };\n};\n\nexports.loop = loop;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.readBits = exports.readArray = exports.readUnsigned = exports.readString = exports.peekBytes = exports.readBytes = exports.peekByte = exports.readByte = exports.buildStream = void 0;\n\n// Default stream and parsers for Uint8TypedArray data type\nvar buildStream = function buildStream(uint8Data) {\n return {\n data: uint8Data,\n pos: 0\n };\n};\n\nexports.buildStream = buildStream;\n\nvar readByte = function readByte() {\n return function (stream) {\n return stream.data[stream.pos++];\n };\n};\n\nexports.readByte = readByte;\n\nvar peekByte = function peekByte() {\n var offset = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;\n return function (stream) {\n return stream.data[stream.pos + offset];\n };\n};\n\nexports.peekByte = peekByte;\n\nvar readBytes = function readBytes(length) {\n return function (stream) {\n return stream.data.subarray(stream.pos, stream.pos += length);\n };\n};\n\nexports.readBytes = readBytes;\n\nvar peekBytes = function peekBytes(length) {\n return function (stream) {\n return stream.data.subarray(stream.pos, stream.pos + length);\n };\n};\n\nexports.peekBytes = peekBytes;\n\nvar readString = function readString(length) {\n return function (stream) {\n return Array.from(readBytes(length)(stream)).map(function (value) {\n return String.fromCharCode(value);\n }).join('');\n };\n};\n\nexports.readString = readString;\n\nvar readUnsigned = function readUnsigned(littleEndian) {\n return function (stream) {\n var bytes = readBytes(2)(stream);\n return littleEndian ? (bytes[1] << 8) + bytes[0] : (bytes[0] << 8) + bytes[1];\n };\n};\n\nexports.readUnsigned = readUnsigned;\n\nvar readArray = function readArray(byteSize, totalOrFunc) {\n return function (stream, result, parent) {\n var total = typeof totalOrFunc === 'function' ? totalOrFunc(stream, result, parent) : totalOrFunc;\n var parser = readBytes(byteSize);\n var arr = new Array(total);\n\n for (var i = 0; i < total; i++) {\n arr[i] = parser(stream);\n }\n\n return arr;\n };\n};\n\nexports.readArray = readArray;\n\nvar subBitsTotal = function subBitsTotal(bits, startIndex, length) {\n var result = 0;\n\n for (var i = 0; i < length; i++) {\n result += bits[startIndex + i] && Math.pow(2, length - i - 1);\n }\n\n return result;\n};\n\nvar readBits = function readBits(schema) {\n return function (stream) {\n var _byte = readByte()(stream); // convert the byte to bit array\n\n\n var bits = new Array(8);\n\n for (var i = 0; i < 8; i++) {\n bits[7 - i] = !!(_byte & 1 << i);\n } // convert the bit array to values based on the schema\n\n\n return Object.keys(schema).reduce(function (res, key) {\n var def = schema[key];\n\n if (def.length) {\n res[key] = subBitsTotal(bits, def.index, def.length);\n } else {\n res[key] = bits[def.index];\n }\n\n return res;\n }, {});\n };\n};\n\nexports.readBits = readBits;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.decompressFrames = exports.decompressFrame = exports.parseGIF = void 0;\n\nvar _gif = _interopRequireDefault(require(\"js-binary-schema-parser/lib/schemas/gif\"));\n\nvar _jsBinarySchemaParser = require(\"js-binary-schema-parser\");\n\nvar _uint = require(\"js-binary-schema-parser/lib/parsers/uint8\");\n\nvar _deinterlace = require(\"./deinterlace\");\n\nvar _lzw = require(\"./lzw\");\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj }; }\n\nvar parseGIF = function parseGIF(arrayBuffer) {\n var byteData = new Uint8Array(arrayBuffer);\n return (0, _jsBinarySchemaParser.parse)((0, _uint.buildStream)(byteData), _gif[\"default\"]);\n};\n\nexports.parseGIF = parseGIF;\n\nvar generatePatch = function generatePatch(image) {\n var totalPixels = image.pixels.length;\n var patchData = new Uint8ClampedArray(totalPixels * 4);\n\n for (var i = 0; i < totalPixels; i++) {\n var pos = i * 4;\n var colorIndex = image.pixels[i];\n var color = image.colorTable[colorIndex] || [0, 0, 0];\n patchData[pos] = color[0];\n patchData[pos + 1] = color[1];\n patchData[pos + 2] = color[2];\n patchData[pos + 3] = colorIndex !== image.transparentIndex ? 255 : 0;\n }\n\n return patchData;\n};\n\nvar decompressFrame = function decompressFrame(frame, gct, buildImagePatch) {\n if (!frame.image) {\n console.warn('gif frame does not have associated image.');\n return;\n }\n\n var image = frame.image; // get the number of pixels\n\n var totalPixels = image.descriptor.width * image.descriptor.height; // do lzw decompression\n\n var pixels = (0, _lzw.lzw)(image.data.minCodeSize, image.data.blocks, totalPixels); // deal with interlacing if necessary\n\n if (image.descriptor.lct.interlaced) {\n pixels = (0, _deinterlace.deinterlace)(pixels, image.descriptor.width);\n }\n\n var resultImage = {\n pixels: pixels,\n dims: {\n top: frame.image.descriptor.top,\n left: frame.image.descriptor.left,\n width: frame.image.descriptor.width,\n height: frame.image.descriptor.height\n }\n }; // color table\n\n if (image.descriptor.lct && image.descriptor.lct.exists) {\n resultImage.colorTable = image.lct;\n } else {\n resultImage.colorTable = gct;\n } // add per frame relevant gce information\n\n\n if (frame.gce) {\n resultImage.delay = (frame.gce.delay || 10) * 10; // convert to ms\n\n resultImage.disposalType = frame.gce.extras.disposal; // transparency\n\n if (frame.gce.extras.transparentColorGiven) {\n resultImage.transparentIndex = frame.gce.transparentColorIndex;\n }\n } // create canvas usable imagedata if desired\n\n\n if (buildImagePatch) {\n resultImage.patch = generatePatch(resultImage);\n }\n\n return resultImage;\n};\n\nexports.decompressFrame = decompressFrame;\n\nvar decompressFrames = function decompressFrames(parsedGif, buildImagePatches) {\n return parsedGif.frames.filter(function (f) {\n return f.image;\n }).map(function (f) {\n return decompressFrame(f, parsedGif.gct, buildImagePatches);\n });\n};\n\nexports.decompressFrames = decompressFrames;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports[\"default\"] = void 0;\n\nvar _ = require(\"../\");\n\nvar _uint = require(\"../parsers/uint8\");\n\n// a set of 0x00 terminated subblocks\nvar subBlocksSchema = {\n blocks: function blocks(stream) {\n var terminator = 0x00;\n var chunks = [];\n var streamSize = stream.data.length;\n var total = 0;\n\n for (var size = (0, _uint.readByte)()(stream); size !== terminator; size = (0, _uint.readByte)()(stream)) {\n // size becomes undefined for some case when file is corrupted and terminator is not proper \n // null check to avoid recursion\n if (!size) break; // catch corrupted files with no terminator\n\n if (stream.pos + size >= streamSize) {\n var availableSize = streamSize - stream.pos;\n chunks.push((0, _uint.readBytes)(availableSize)(stream));\n total += availableSize;\n break;\n }\n\n chunks.push((0, _uint.readBytes)(size)(stream));\n total += size;\n }\n\n var result = new Uint8Array(total);\n var offset = 0;\n\n for (var i = 0; i < chunks.length; i++) {\n result.set(chunks[i], offset);\n offset += chunks[i].length;\n }\n\n return result;\n }\n}; // global control extension\n\nvar gceSchema = (0, _.conditional)({\n gce: [{\n codes: (0, _uint.readBytes)(2)\n }, {\n byteSize: (0, _uint.readByte)()\n }, {\n extras: (0, _uint.readBits)({\n future: {\n index: 0,\n length: 3\n },\n disposal: {\n index: 3,\n length: 3\n },\n userInput: {\n index: 6\n },\n transparentColorGiven: {\n index: 7\n }\n })\n }, {\n delay: (0, _uint.readUnsigned)(true)\n }, {\n transparentColorIndex: (0, _uint.readByte)()\n }, {\n terminator: (0, _uint.readByte)()\n }]\n}, function (stream) {\n var codes = (0, _uint.peekBytes)(2)(stream);\n return codes[0] === 0x21 && codes[1] === 0xf9;\n}); // image pipeline block\n\nvar imageSchema = (0, _.conditional)({\n image: [{\n code: (0, _uint.readByte)()\n }, {\n descriptor: [{\n left: (0, _uint.readUnsigned)(true)\n }, {\n top: (0, _uint.readUnsigned)(true)\n }, {\n width: (0, _uint.readUnsigned)(true)\n }, {\n height: (0, _uint.readUnsigned)(true)\n }, {\n lct: (0, _uint.readBits)({\n exists: {\n index: 0\n },\n interlaced: {\n index: 1\n },\n sort: {\n index: 2\n },\n future: {\n index: 3,\n length: 2\n },\n size: {\n index: 5,\n length: 3\n }\n })\n }]\n }, (0, _.conditional)({\n lct: (0, _uint.readArray)(3, function (stream, result, parent) {\n return Math.pow(2, parent.descriptor.lct.size + 1);\n })\n }, function (stream, result, parent) {\n return parent.descriptor.lct.exists;\n }), {\n data: [{\n minCodeSize: (0, _uint.readByte)()\n }, subBlocksSchema]\n }]\n}, function (stream) {\n return (0, _uint.peekByte)()(stream) === 0x2c;\n}); // plain text block\n\nvar textSchema = (0, _.conditional)({\n text: [{\n codes: (0, _uint.readBytes)(2)\n }, {\n blockSize: (0, _uint.readByte)()\n }, {\n preData: function preData(stream, result, parent) {\n return (0, _uint.readBytes)(parent.text.blockSize)(stream);\n }\n }, subBlocksSchema]\n}, function (stream) {\n var codes = (0, _uint.peekBytes)(2)(stream);\n return codes[0] === 0x21 && codes[1] === 0x01;\n}); // application block\n\nvar applicationSchema = (0, _.conditional)({\n application: [{\n codes: (0, _uint.readBytes)(2)\n }, {\n blockSize: (0, _uint.readByte)()\n }, {\n id: function id(stream, result, parent) {\n return (0, _uint.readString)(parent.blockSize)(stream);\n }\n }, subBlocksSchema]\n}, function (stream) {\n var codes = (0, _uint.peekBytes)(2)(stream);\n return codes[0] === 0x21 && codes[1] === 0xff;\n}); // comment block\n\nvar commentSchema = (0, _.conditional)({\n comment: [{\n codes: (0, _uint.readBytes)(2)\n }, subBlocksSchema]\n}, function (stream) {\n var codes = (0, _uint.peekBytes)(2)(stream);\n return codes[0] === 0x21 && codes[1] === 0xfe;\n});\nvar schema = [{\n header: [{\n signature: (0, _uint.readString)(3)\n }, {\n version: (0, _uint.readString)(3)\n }]\n}, {\n lsd: [{\n width: (0, _uint.readUnsigned)(true)\n }, {\n height: (0, _uint.readUnsigned)(true)\n }, {\n gct: (0, _uint.readBits)({\n exists: {\n index: 0\n },\n resolution: {\n index: 1,\n length: 3\n },\n sort: {\n index: 4\n },\n size: {\n index: 5,\n length: 3\n }\n })\n }, {\n backgroundColorIndex: (0, _uint.readByte)()\n }, {\n pixelAspectRatio: (0, _uint.readByte)()\n }]\n}, (0, _.conditional)({\n gct: (0, _uint.readArray)(3, function (stream, result) {\n return Math.pow(2, result.lsd.gct.size + 1);\n })\n}, function (stream, result) {\n return result.lsd.gct.exists;\n}), // content frames\n{\n frames: (0, _.loop)([gceSchema, applicationSchema, commentSchema, imageSchema, textSchema], function (stream) {\n var nextCode = (0, _uint.peekByte)()(stream); // rather than check for a terminator, we should check for the existence\n // of an ext or image block to avoid infinite loops\n //var terminator = 0x3B;\n //return nextCode !== terminator;\n\n return nextCode === 0x21 || nextCode === 0x2c;\n })\n}];\nvar _default = schema;\nexports[\"default\"] = _default;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.deinterlace = void 0;\n\n/**\r\n * Deinterlace function from https://github.com/shachaf/jsgif\r\n */\nvar deinterlace = function deinterlace(pixels, width) {\n var newPixels = new Array(pixels.length);\n var rows = pixels.length / width;\n\n var cpRow = function cpRow(toRow, fromRow) {\n var fromPixels = pixels.slice(fromRow * width, (fromRow + 1) * width);\n newPixels.splice.apply(newPixels, [toRow * width, width].concat(fromPixels));\n }; // See appendix E.\n\n\n var offsets = [0, 4, 2, 1];\n var steps = [8, 8, 4, 2];\n var fromRow = 0;\n\n for (var pass = 0; pass < 4; pass++) {\n for (var toRow = offsets[pass]; toRow < rows; toRow += steps[pass]) {\n cpRow(toRow, fromRow);\n fromRow++;\n }\n }\n\n return newPixels;\n};\n\nexports.deinterlace = deinterlace;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.lzw = void 0;\n\n/**\r\n * javascript port of java LZW decompression\r\n * Original java author url: https://gist.github.com/devunwired/4479231\r\n */\nvar lzw = function lzw(minCodeSize, data, pixelCount) {\n var MAX_STACK_SIZE = 4096;\n var nullCode = -1;\n var npix = pixelCount;\n var available, clear, code_mask, code_size, end_of_information, in_code, old_code, bits, code, i, datum, data_size, first, top, bi, pi;\n var dstPixels = new Array(pixelCount);\n var prefix = new Array(MAX_STACK_SIZE);\n var suffix = new Array(MAX_STACK_SIZE);\n var pixelStack = new Array(MAX_STACK_SIZE + 1); // Initialize GIF data stream decoder.\n\n data_size = minCodeSize;\n clear = 1 << data_size;\n end_of_information = clear + 1;\n available = clear + 2;\n old_code = nullCode;\n code_size = data_size + 1;\n code_mask = (1 << code_size) - 1;\n\n for (code = 0; code < clear; code++) {\n prefix[code] = 0;\n suffix[code] = code;\n } // Decode GIF pixel stream.\n\n\n var datum, bits, count, first, top, pi, bi;\n datum = bits = count = first = top = pi = bi = 0;\n\n for (i = 0; i < npix;) {\n if (top === 0) {\n if (bits < code_size) {\n // get the next byte\n datum += data[bi] << bits;\n bits += 8;\n bi++;\n continue;\n } // Get the next code.\n\n\n code = datum & code_mask;\n datum >>= code_size;\n bits -= code_size; // Interpret the code\n\n if (code > available || code == end_of_information) {\n break;\n }\n\n if (code == clear) {\n // Reset decoder.\n code_size = data_size + 1;\n code_mask = (1 << code_size) - 1;\n available = clear + 2;\n old_code = nullCode;\n continue;\n }\n\n if (old_code == nullCode) {\n pixelStack[top++] = suffix[code];\n old_code = code;\n first = code;\n continue;\n }\n\n in_code = code;\n\n if (code == available) {\n pixelStack[top++] = first;\n code = old_code;\n }\n\n while (code > clear) {\n pixelStack[top++] = suffix[code];\n code = prefix[code];\n }\n\n first = suffix[code] & 0xff;\n pixelStack[top++] = first; // add a new string to the table, but only if space is available\n // if not, just continue with current table until a clear code is found\n // (deferred clear code implementation as per GIF spec)\n\n if (available < MAX_STACK_SIZE) {\n prefix[available] = old_code;\n suffix[available] = first;\n available++;\n\n if ((available & code_mask) === 0 && available < MAX_STACK_SIZE) {\n code_size++;\n code_mask += available;\n }\n }\n\n old_code = in_code;\n } // Pop a pixel off the pixel stack.\n\n\n top--;\n dstPixels[pi++] = pixelStack[top];\n i++;\n }\n\n for (i = pi; i < npix; i++) {\n dstPixels[i] = 0; // clear missing pixels\n }\n\n return dstPixels;\n};\n\nexports.lzw = lzw;","/**\n * Animated GIF Helper for PlayCanvas\n *\n * Uses gifuct-js to parse and decompress GIF frames, then renders them\n * to a PlayCanvas texture with proper transparency support.\n */\nimport * as pc from 'playcanvas';\nimport { parseGIF, decompressFrames } from 'gifuct-js';\n/**\n * Represents a single GIF frame\n */\ninterface GifFrame {\n dims: {\n width: number;\n height: number;\n top: number;\n left: number;\n };\n patch: Uint8ClampedArray;\n delay: number;\n disposalType?: number;\n}\n/**\n * Options for creating an animated GIF texture\n */\nexport interface AnimatedGifOptions {\n /** Auto-play the GIF when loaded */\n autoPlay?: boolean;\n /** Callback when the first frame is ready */\n onReady?: () => void;\n /** Callback on error */\n onError?: (error: Error) => void;\n}\n/**\n * Animated GIF texture for PlayCanvas\n * Handles parsing, decompression, and frame-by-frame rendering with transparency\n */\nexport class AnimatedGifTexture {\n private app: pc.Application;\n private url: string;\n private frames: GifFrame[] = [];\n private currentFrameIndex = 0;\n private isPlaying = false;\n private isLoaded = false;\n private lastFrameTime = 0;\n private updateHandler: (() => void) | null = null;\n private options: AnimatedGifOptions;\n // Canvas for compositing frames (handles disposal and transparency)\n private canvas: HTMLCanvasElement;\n private ctx: CanvasRenderingContext2D;\n // PlayCanvas texture\n public texture: pc.Texture | null = null;\n // GIF dimensions\n private gifWidth = 0;\n private gifHeight = 0;\n constructor(app: pc.Application, url: string, options: AnimatedGifOptions = {}) {\n this.app = app;\n this.url = url;\n this.options = options;\n // Create offscreen canvas for compositing\n this.canvas = document.createElement('canvas');\n this.ctx = this.canvas.getContext('2d', { willReadFrequently: true })!;\n // Start loading\n this.load();\n }\n /**\n * Load and parse the GIF\n */\n private async load(): Promise<void> {\n try {\n // Fetch GIF data\n const response = await fetch(this.url);\n if (!response.ok) {\n throw new Error(`Failed to fetch GIF: ${response.statusText}`);\n }\n const buffer = await response.arrayBuffer();\n // Parse GIF\n const gif = parseGIF(buffer);\n this.frames = decompressFrames(gif, true) as unknown as GifFrame[];\n if (this.frames.length === 0) {\n throw new Error('GIF has no frames');\n }\n // Get dimensions from first frame\n this.gifWidth = gif.lsd.width;\n this.gifHeight = gif.lsd.height;\n // Setup canvas\n this.canvas.width = this.gifWidth;\n this.canvas.height = this.gifHeight;\n // Create PlayCanvas texture\n this.texture = new pc.Texture(this.app.graphicsDevice, {\n width: this.gifWidth,\n height: this.gifHeight,\n format: pc.PIXELFORMAT_RGBA8,\n mipmaps: false,\n minFilter: pc.FILTER_LINEAR,\n magFilter: pc.FILTER_LINEAR,\n addressU: pc.ADDRESS_CLAMP_TO_EDGE,\n addressV: pc.ADDRESS_CLAMP_TO_EDGE\n });\n // Draw first frame\n this.drawFrame(0);\n this.isLoaded = true;\n console.log(`[AnimatedGif] Loaded GIF: ${this.url}, ${this.frames.length} frames, ${this.gifWidth}x${this.gifHeight}`);\n // Notify ready\n if (this.options.onReady) {\n this.options.onReady();\n }\n // Auto-play if requested\n if (this.options.autoPlay) {\n this.play();\n }\n }\n catch (error) {\n console.error('[AnimatedGif] Error loading GIF:', error);\n if (this.options.onError) {\n this.options.onError(error instanceof Error ? error : new Error(String(error)));\n }\n }\n }\n /**\n * Draw a specific frame to the canvas and update the texture\n */\n private drawFrame(frameIndex: number): void {\n if (!this.texture || frameIndex >= this.frames.length)\n return;\n const frame = this.frames[frameIndex];\n const prevFrame = frameIndex > 0 ? this.frames[frameIndex - 1] : null;\n // Handle disposal of previous frame\n if (prevFrame && prevFrame.disposalType === 2) {\n // Restore to background (clear the previous frame area)\n this.ctx.clearRect(prevFrame.dims.left, prevFrame.dims.top, prevFrame.dims.width, prevFrame.dims.height);\n }\n // disposalType === 3 (restore to previous) is complex and rarely used, skip for now\n // Create ImageData from frame patch\n const imageData = new ImageData(new Uint8ClampedArray(frame.patch), frame.dims.width, frame.dims.height);\n // Create temporary canvas for this frame\n const tempCanvas = document.createElement('canvas');\n tempCanvas.width = frame.dims.width;\n tempCanvas.height = frame.dims.height;\n const tempCtx = tempCanvas.getContext('2d')!;\n tempCtx.putImageData(imageData, 0, 0);\n // Draw frame patch at correct position\n this.ctx.drawImage(tempCanvas, frame.dims.left, frame.dims.top);\n // Update texture from canvas\n this.updateTexture();\n }\n /**\n * Update the PlayCanvas texture from the canvas\n */\n private updateTexture(): void {\n if (!this.texture)\n return;\n // Get pixel data from canvas\n const imageData = this.ctx.getImageData(0, 0, this.gifWidth, this.gifHeight);\n // Upload to texture\n const pixels = this.texture.lock();\n if (pixels) {\n pixels.set(imageData.data);\n }\n this.texture.unlock();\n this.texture.upload();\n }\n /**\n * Animation update - called each frame when playing\n */\n private update = (): void => {\n if (!this.isPlaying || !this.isLoaded || this.frames.length <= 1)\n return;\n const now = performance.now();\n const frame = this.frames[this.currentFrameIndex];\n const delay = frame.delay || 100; // Default 100ms if not specified\n if (now - this.lastFrameTime >= delay) {\n // Advance to next frame\n this.currentFrameIndex = (this.currentFrameIndex + 1) % this.frames.length;\n this.drawFrame(this.currentFrameIndex);\n this.lastFrameTime = now;\n }\n };\n /**\n * Start playing the GIF animation\n */\n public play(): void {\n if (this.isPlaying)\n return;\n this.isPlaying = true;\n this.lastFrameTime = performance.now();\n // Register update handler\n if (!this.updateHandler) {\n this.updateHandler = this.update;\n this.app.on('update', this.updateHandler);\n }\n }\n /**\n * Pause the GIF animation\n */\n public pause(): void {\n if (!this.isPlaying)\n return;\n this.isPlaying = false;\n // Remove update handler\n if (this.updateHandler) {\n this.app.off('update', this.updateHandler);\n this.updateHandler = null;\n }\n }\n /**\n * Stop and reset to first frame\n */\n public stop(): void {\n this.pause();\n this.currentFrameIndex = 0;\n if (this.isLoaded) {\n // Clear canvas and redraw first frame\n this.ctx.clearRect(0, 0, this.gifWidth, this.gifHeight);\n this.drawFrame(0);\n }\n }\n /**\n * Check if the GIF is currently playing\n */\n public get playing(): boolean {\n return this.isPlaying;\n }\n /**\n * Check if the GIF is loaded\n */\n public get loaded(): boolean {\n return this.isLoaded;\n }\n /**\n * Clean up resources\n */\n public destroy(): void {\n this.pause();\n if (this.texture) {\n this.texture.destroy();\n this.texture = null;\n }\n this.frames = [];\n this.isLoaded = false;\n this.canvas = null!;\n this.ctx = null!;\n }\n}\n/**\n * Helper to detect if a URL is a GIF\n */\nexport function isGifUrl(url: string): boolean {\n const lower = url.toLowerCase();\n // Check extension or content type hint\n return lower.endsWith('.gif') || lower.includes('image/gif') || lower.includes('format=gif');\n}\n","/**\n * HTML Mesh Helper for PlayCanvas\n *\n * Renders HTML elements as textures on 3D meshes using the texElement2D API\n * (HTML-in-Canvas proposal) with fallback to canvas rendering.\n *\n * Based on PlayCanvas PR #7897: https://github.com/playcanvas/engine/pull/7897\n */\nimport * as pc from 'playcanvas';\ntype GraphicsDeviceWithHtmlSupport = pc.GraphicsDevice & {\n supportsTexElement2D?: boolean;\n _isHTMLElementInterface?: (texture: unknown) => boolean;\n _isBrowserInterface?: (texture: unknown) => boolean;\n};\ninterface GraphicsDeviceConstructorWithHtmlSupport {\n prototype: GraphicsDeviceWithHtmlSupport;\n}\ninterface TextureWithHTMLElementSource extends pc.Texture {\n setSource(source: unknown): void;\n}\ninterface HtmlMeshEntity extends pc.Entity {\n _billboardActive?: boolean;\n}\n/**\n * Patch PlayCanvas GraphicsDevice to add _isHTMLElementInterface method\n * This is required for HTML-in-Canvas support (texElement2D API)\n *\n * The postinstall patches only modify ESM source files, but production builds\n * may use the bundled playcanvas.js which doesn't have the patches.\n * This runtime patch ensures the method exists regardless of which build is used.\n */\nfunction patchPlayCanvasForHtmlMesh(): void {\n // Get the GraphicsDevice class from pc\n const GraphicsDevice = (pc as typeof pc & {\n GraphicsDevice?: GraphicsDeviceConstructorWithHtmlSupport;\n }).GraphicsDevice;\n if (!GraphicsDevice) {\n console.warn('[HtmlMeshHelper] Could not find pc.GraphicsDevice to patch');\n return;\n }\n // Check if already patched\n if (typeof GraphicsDevice.prototype._isHTMLElementInterface === 'function') {\n return; // Already patched\n }\n // Add the _isHTMLElementInterface method\n GraphicsDevice.prototype._isHTMLElementInterface = function (texture: unknown): boolean {\n return typeof HTMLElement !== 'undefined' &&\n texture instanceof HTMLElement &&\n !(texture instanceof HTMLImageElement) &&\n !(texture instanceof HTMLCanvasElement) &&\n !(texture instanceof HTMLVideoElement);\n };\n // Patch _isBrowserInterface to include our new method\n const originalIsBrowserInterface = GraphicsDevice.prototype._isBrowserInterface;\n if (originalIsBrowserInterface) {\n GraphicsDevice.prototype._isBrowserInterface = function (texture: unknown): boolean {\n return originalIsBrowserInterface.call(this, texture) ||\n this._isHTMLElementInterface!(texture);\n };\n }\n console.log('[HtmlMeshHelper] Patched PlayCanvas GraphicsDevice for HTML-in-Canvas support');\n}\n/**\n * HTML Mesh configuration\n */\nexport interface HtmlMeshConfig {\n /** Unique ID for the mesh */\n id: string;\n /** HTML content to render */\n html?: string;\n /** Raw HTML content (alias for html) */\n htmlContent?: string;\n /** CSS styles for the container (can reference external stylesheets) */\n css?: string;\n /** Position in world space */\n position: {\n x: number;\n y: number;\n z: number;\n };\n /** Rotation in degrees */\n rotation?: {\n x: number;\n y: number;\n z: number;\n };\n /** Scale of the mesh */\n scale?: {\n x: number;\n y: number;\n z: number;\n };\n /** Width in pixels (default: 512) */\n width?: number;\n /** Height in pixels (default: 512) */\n height?: number;\n /** Whether to update the texture every frame (for animations) */\n animated?: boolean;\n /** Update rate in milliseconds (default: 100ms for animated) */\n updateRate?: number;\n /** Whether to face the camera (billboard mode) */\n billboard?: boolean;\n /** Billboard range control (percentage or waypoint based) */\n billboardRange?: {\n type: 'percentage' | 'waypoint';\n start: number;\n end: number;\n };\n /** Visibility range control */\n visibilityRange?: {\n type: 'percentage' | 'waypoint';\n start: number;\n end: number;\n };\n /** Whether to receive shadows */\n receiveShadows?: boolean;\n /** Whether to cast shadows */\n castShadows?: boolean;\n /** Opacity (0-1) */\n opacity?: number;\n /** Whether the mesh is double-sided */\n doubleSided?: boolean;\n}\n/**\n * HTML Mesh instance\n */\nexport interface HtmlMeshInstance {\n entity: pc.Entity;\n texture: pc.Texture;\n material: pc.StandardMaterial;\n htmlElement: HTMLElement;\n config: HtmlMeshConfig;\n destroy: () => void;\n update: () => void;\n}\n/**\n * Creates and manages HTML meshes in a PlayCanvas scene\n */\nexport class HtmlMeshManager {\n private app: pc.Application;\n private canvas: HTMLCanvasElement;\n private meshes: Map<string, HtmlMeshInstance> = new Map();\n private updateHandler: (() => void) | null = null;\n private useTexElement2D: boolean = false;\n constructor(app: pc.Application) {\n this.app = app;\n this.canvas = app.graphicsDevice.canvas as HTMLCanvasElement;\n // Apply runtime patch to PlayCanvas for HTML-in-Canvas support\n // This ensures _isHTMLElementInterface exists even if the bundled build is used\n patchPlayCanvasForHtmlMesh();\n // Check if texElement2D is supported\n const device = app.graphicsDevice as GraphicsDeviceWithHtmlSupport;\n this.useTexElement2D = device.supportsTexElement2D === true;\n console.log(`[HtmlMeshManager] texElement2D support: ${this.useTexElement2D}`);\n }\n /**\n * Create an HTML mesh from config\n */\n createMesh(config: HtmlMeshConfig): HtmlMeshInstance {\n // Ensure width/height are valid pixel integers (scene data may have scale values like 1.9)\n let width = config.width || 512;\n let height = config.height || 512;\n if (width < 4) width = 512;\n if (height < 4) height = 512;\n width = Math.round(width);\n height = Math.round(height);\n // Create HTML element container\n const htmlElement = this.createHtmlElement(config, width, height);\n // Create texture\n const texture = this.createTexture(htmlElement, width, height, config);\n // Create material\n const material = this.createMaterial(texture, config);\n // Create entity with plane mesh\n const entity = this.createEntity(config, material, width, height);\n // Setup update logic if animated\n const instance: HtmlMeshInstance = {\n entity,\n texture,\n material,\n htmlElement,\n config,\n destroy: () => this.destroyMesh(config.id),\n update: () => this.updateMeshTexture(config.id)\n };\n this.meshes.set(config.id, instance);\n // Setup animation updates if needed\n if (config.animated && !this.updateHandler) {\n this.startUpdateLoop();\n }\n return instance;\n }\n /**\n * Create the HTML element for the mesh\n */\n private createHtmlElement(config: HtmlMeshConfig, width: number, height: number): HTMLElement {\n const container = document.createElement('div');\n container.id = `html-mesh-${config.id}`;\n container.style.width = `${width}px`;\n container.style.height = `${height}px`;\n container.style.position = 'absolute';\n container.style.top = '0';\n container.style.left = '0';\n container.style.pointerEvents = 'none';\n container.style.zIndex = '-1';\n container.style.overflow = 'hidden';\n // Add custom CSS if provided\n if (config.css) {\n const style = document.createElement('style');\n style.textContent = config.css;\n container.appendChild(style);\n }\n // Set HTML content\n container.innerHTML += config.html;\n // For texElement2D, the element must be a child of the canvas\n if (this.useTexElement2D) {\n // Enable layoutsubtree for HTML-in-Canvas support\n this.canvas.setAttribute('layoutsubtree', '');\n this.canvas.setAttribute('data-layoutsubtree', '');\n this.canvas.appendChild(container);\n }\n else {\n // For fallback, we can keep it hidden in the body\n container.style.visibility = 'hidden';\n document.body.appendChild(container);\n }\n return container;\n }\n /**\n * Create texture from HTML element\n */\n private createTexture(htmlElement: HTMLElement, width: number, height: number, config: HtmlMeshConfig): pc.Texture {\n const texture = new pc.Texture(this.app.graphicsDevice, {\n width,\n height,\n format: pc.PIXELFORMAT_RGBA8,\n mipmaps: false,\n minFilter: pc.FILTER_LINEAR,\n magFilter: pc.FILTER_LINEAR,\n addressU: pc.ADDRESS_CLAMP_TO_EDGE,\n addressV: pc.ADDRESS_CLAMP_TO_EDGE,\n name: `htmlMesh-${config.id}`\n });\n // Set the HTML element as the texture source\n if (this.useTexElement2D) {\n try {\n (texture as TextureWithHTMLElementSource).setSource(htmlElement);\n console.log(`[HtmlMeshManager] Using texElement2D for mesh ${config.id}`);\n }\n catch (error) {\n console.warn(`[HtmlMeshManager] texElement2D failed, falling back to canvas: ${error}`);\n this.renderToCanvas(texture, htmlElement, width, height);\n }\n }\n else {\n this.renderToCanvas(texture, htmlElement, width, height);\n }\n return texture;\n }\n /**\n * Fallback: Render HTML to canvas and use as texture source\n */\n private renderToCanvas(texture: pc.Texture, htmlElement: HTMLElement, width: number, height: number): void {\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext('2d', { willReadFrequently: true })!;\n // Create an SVG foreignObject containing the HTML\n const svg = `\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\">\n <foreignObject width=\"100%\" height=\"100%\">\n <div xmlns=\"http://www.w3.org/1999/xhtml\" style=\"width:${width}px;height:${height}px;\">\n ${htmlElement.innerHTML}\n </div>\n </foreignObject>\n </svg>\n `;\n const img = new Image();\n const blob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' });\n const url = URL.createObjectURL(blob);\n img.onload = () => {\n ctx.drawImage(img, 0, 0);\n URL.revokeObjectURL(url);\n\n // Check if the canvas is tainted (cross-origin resources in the SVG\n // foreignObject will taint it). A tainted canvas MUST NOT be passed\n // to texture.setSource() because PlayCanvas will later call texImage2D\n // on it every frame, throwing an uncaught SecurityError each time.\n let isTainted = false;\n try {\n canvas.toDataURL();\n } catch {\n isTainted = true;\n }\n if (isTainted) {\n console.warn(`[HtmlMeshManager] Canvas tainted by cross-origin content, using clean fallback`);\n }\n\n // Always use a clean canvas for the texture to avoid taint issues.\n // SVG foreignObject with HTML content frequently triggers taint in browsers\n // even when toDataURL() doesn't detect it synchronously (the taint may\n // manifest later during WebGL texImage2D upload in the render loop).\n // The clean fallback avoids the per-frame SecurityError spam entirely.\n const fallback = document.createElement('canvas');\n fallback.width = width;\n fallback.height = height;\n const fctx = fallback.getContext('2d')!;\n if (!isTainted) {\n // Draw the rendered content onto the clean fallback canvas\n fctx.drawImage(canvas, 0, 0);\n // Now check if THIS canvas is also tainted\n try {\n fallback.toDataURL();\n texture.setSource(fallback);\n } catch {\n console.warn(`[HtmlMeshManager] Fallback canvas also tainted, using placeholder`);\n fctx.fillStyle = '#333';\n fctx.fillRect(0, 0, width, height);\n fctx.fillStyle = '#fff';\n fctx.font = '20px Arial';\n fctx.textAlign = 'center';\n fctx.fillText('HTML Mesh', width / 2, height / 2);\n texture.setSource(fallback);\n }\n } else {\n fctx.fillStyle = '#333';\n fctx.fillRect(0, 0, width, height);\n fctx.fillStyle = '#fff';\n fctx.font = '20px Arial';\n fctx.textAlign = 'center';\n fctx.fillText('HTML Mesh', width / 2, height / 2);\n texture.setSource(fallback);\n }\n };\n img.onerror = () => {\n // Fallback: just draw a colored rectangle with text\n ctx.fillStyle = '#333';\n ctx.fillRect(0, 0, width, height);\n ctx.fillStyle = '#fff';\n ctx.font = '20px Arial';\n ctx.textAlign = 'center';\n ctx.fillText('HTML Mesh', width / 2, height / 2);\n URL.revokeObjectURL(url);\n texture.setSource(canvas);\n };\n img.src = url;\n }\n /**\n * Create material for the mesh\n */\n private createMaterial(texture: pc.Texture, config: HtmlMeshConfig): pc.StandardMaterial {\n const material = new pc.StandardMaterial();\n material.diffuseMap = texture;\n material.emissiveMap = texture;\n material.emissive = new pc.Color(0.5, 0.5, 0.5);\n material.opacity = config.opacity ?? 1;\n material.blendType = config.opacity !== undefined && config.opacity < 1 ? pc.BLEND_NORMAL : pc.BLEND_NONE;\n material.cull = config.doubleSided ? pc.CULLFACE_NONE : pc.CULLFACE_BACK;\n material.update();\n return material;\n }\n /**\n * Create entity with plane mesh\n */\n private createEntity(config: HtmlMeshConfig, material: pc.StandardMaterial, width: number, height: number): pc.Entity {\n const entity = new pc.Entity(`htmlMesh-${config.id}`);\n // Calculate aspect ratio for the plane\n const aspect = width / height;\n const baseSize = 1;\n entity.addComponent('render', {\n type: 'plane',\n material,\n castShadows: config.castShadows ?? false,\n receiveShadows: config.receiveShadows ?? false\n });\n // Set transform\n entity.setPosition(config.position.x, config.position.y, config.position.z);\n const rotation = config.rotation || { x: 0, y: 0, z: 0 };\n entity.setEulerAngles(rotation.x, rotation.y, rotation.z);\n const scale = config.scale || { x: 1, y: 1, z: 1 };\n entity.setLocalScale(scale.x * aspect, 1, scale.y);\n this.app.root.addChild(entity);\n // Setup billboard if requested\n if (config.billboard) {\n this.app.on('update', () => {\n if (!entity.enabled)\n return;\n const camera = this.app.root.findComponent('camera')?.entity;\n if (camera) {\n entity.lookAt(camera.getPosition());\n }\n });\n }\n return entity;\n }\n /**\n * Update a mesh's texture\n */\n updateMeshTexture(id: string): void {\n const instance = this.meshes.get(id);\n if (!instance)\n return;\n if (this.useTexElement2D) {\n // For texElement2D, just call upload to refresh\n instance.texture.upload();\n }\n else {\n // For canvas fallback, re-render (sanitize dimensions same as createMesh)\n let w = instance.config.width || 512;\n let h = instance.config.height || 512;\n if (w < 4) w = 512;\n if (h < 4) h = 512;\n this.renderToCanvas(instance.texture, instance.htmlElement, Math.round(w), Math.round(h));\n }\n }\n /**\n * Start the update loop for animated meshes\n */\n private startUpdateLoop(): void {\n let lastUpdate: {\n [id: string]: number;\n } = {};\n this.updateHandler = () => {\n const now = performance.now();\n this.meshes.forEach((instance, id) => {\n if (!instance.config.animated)\n return;\n const rate = instance.config.updateRate || 100;\n const last = lastUpdate[id] || 0;\n if (now - last >= rate) {\n this.updateMeshTexture(id);\n lastUpdate[id] = now;\n }\n });\n };\n this.app.on('update', this.updateHandler);\n }\n /**\n * Destroy a mesh\n */\n destroyMesh(id: string): void {\n const instance = this.meshes.get(id);\n if (!instance)\n return;\n // Remove entity\n instance.entity.destroy();\n // Destroy texture\n instance.texture.destroy();\n // Remove HTML element\n instance.htmlElement.remove();\n this.meshes.delete(id);\n // Stop update loop if no animated meshes left\n const hasAnimated = Array.from(this.meshes.values()).some(m => m.config.animated);\n if (!hasAnimated && this.updateHandler) {\n this.app.off('update', this.updateHandler);\n this.updateHandler = null;\n }\n }\n /**\n * Get a mesh instance by ID\n */\n getMesh(id: string): HtmlMeshInstance | undefined {\n return this.meshes.get(id);\n }\n /**\n * Update visibility and billboard state based on progress\n * Called from the main viewer to sync with scroll/waypoint progress\n */\n updateVisibility(scrollPercent: number, waypointIndex: number): void {\n this.meshes.forEach((instance) => {\n const config = instance.config;\n // Update visibility based on range\n if (config.visibilityRange) {\n const range = config.visibilityRange;\n let visible = true;\n if (range.type === 'percentage') {\n visible = scrollPercent >= range.start && scrollPercent <= range.end;\n }\n else if (range.type === 'waypoint') {\n visible = waypointIndex >= range.start && waypointIndex <= range.end;\n }\n instance.entity.enabled = visible;\n }\n // Update billboard state based on range\n if (config.billboard && config.billboardRange) {\n const range = config.billboardRange;\n let billboardActive = false;\n if (range.type === 'percentage') {\n billboardActive = scrollPercent >= range.start && scrollPercent <= range.end;\n }\n else if (range.type === 'waypoint') {\n billboardActive = waypointIndex >= range.start && waypointIndex <= range.end;\n }\n // Store billboard active state for the update loop\n (instance.entity as HtmlMeshEntity)._billboardActive = billboardActive;\n }\n else if (config.billboard) {\n // Billboard always active if no range\n (instance.entity as HtmlMeshEntity)._billboardActive = true;\n }\n });\n }\n /**\n * Get all mesh instances\n */\n getAllMeshes(): Map<string, HtmlMeshInstance> {\n return this.meshes;\n }\n /**\n * Destroy all meshes and cleanup\n */\n destroy(): void {\n this.meshes.forEach((_, id) => this.destroyMesh(id));\n if (this.updateHandler) {\n this.app.off('update', this.updateHandler);\n this.updateHandler = null;\n }\n }\n}\n/**\n * Setup HTML meshes from config array\n */\nexport function setupHtmlMeshes(app: pc.Application, htmlMeshes: HtmlMeshConfig[]): HtmlMeshManager {\n const manager = new HtmlMeshManager(app);\n for (const config of htmlMeshes) {\n manager.createMesh(config);\n }\n return manager;\n}\n","/**\n * Custom Script System for PlayCanvas\n *\n * Provides runtime execution of user-defined scripts in the viewer.\n * Adapted from the HTML export system for PlayCanvas engine.\n */\nimport * as pc from 'playcanvas';\n/**\n * API exposed to custom scripts\n */\nexport interface CustomScriptAPI {\n /** PlayCanvas Application instance */\n app: pc.Application;\n /** Camera entity */\n camera: pc.Entity;\n /** PlayCanvas namespace */\n pc: typeof pc;\n /** Canvas element */\n canvas: HTMLCanvasElement;\n /** Get current scroll/progress percentage (0-1) */\n getScrollPercentage: () => number;\n /** Get current waypoint index */\n getCurrentWaypointIndex: () => number;\n /** Get all hotspot entities */\n getHotspots: () => pc.Entity[];\n /** Get splat mesh entities */\n getSplats: () => pc.Entity[];\n /** Get HTML mesh instances */\n getHTMLMeshes: () => unknown[];\n /** Register a cleanup function to be called on script disposal */\n registerCleanup?: (fn: () => void) => void;\n}\n/**\n * Custom Script System\n *\n * Executes user-provided scripts with access to the viewer API.\n * Includes safety measures like sandboxing and cleanup tracking.\n */\nexport class CustomScriptSystem {\n private customScript: string;\n private api: CustomScriptAPI;\n private isInitialized: boolean = false;\n private scriptCleanup: (() => void)[] = [];\n private lastError: unknown = null;\n private updateCallbacks: (() => void)[] = [];\n constructor(customScript: string) {\n this.customScript = customScript;\n this.api = {} as CustomScriptAPI;\n }\n /**\n * Initialize the script system with the viewer API\n */\n initialize(api: CustomScriptAPI): void {\n this.api = {\n ...api,\n registerCleanup: (fn: () => void) => this.addCleanup(fn)\n };\n this.isInitialized = true;\n }\n /**\n * Update the script content and re-execute\n */\n updateScript(script: string): void {\n if (script === this.customScript)\n return;\n this.customScript = script;\n this.execute();\n }\n /**\n * Add a cleanup function to be called on disposal\n */\n private addCleanup(fn: () => void): void {\n if (typeof fn === 'function') {\n this.scriptCleanup.push(fn);\n }\n }\n /**\n * Sanitize script to remove problematic characters\n */\n private sanitizeScript(script: string): string {\n if (!script)\n return '';\n let s = script.normalize('NFC');\n // Remove BOM\n s = s.replace(/\\uFEFF/g, '');\n // Replace non-breaking space with regular space\n s = s.replace(/\\u00A0/g, ' ');\n // Replace unicode line/paragraph separators with newlines\n s = s.replace(/[\\u2028\\u2029]/g, '\\n');\n // Remove zero-width characters\n s = s.replace(/[\\u200B-\\u200D\\u2060]/g, '');\n // Normalize curly quotes\n s = s.replace(/[\\u2018\\u2019\\u201B]/g, \"'\").replace(/[\\u201C\\u201D\\u201E]/g, '\"');\n return s;\n }\n /**\n * Preprocess script with safety wrapping\n */\n private preprocessScript(script: string): string {\n if (!script)\n return '';\n let processed = this.sanitizeScript(script).trim().replace(/\\r\\n/g, '\\n');\n // Warn about common typo\n if (/console\\/log\\s*\\(/.test(processed)) {\n console.warn(\"[Custom Script] Detected 'console/log(...)'. Did you mean 'console.log(...)'?\");\n processed = processed.replace(/console\\/log\\s*\\(/g, 'console.log(');\n }\n // Wrap in try-catch for safety\n processed = \"try {\\n\" + processed + \"\\n} catch (error) {\\n console.error('[Custom Script] Runtime error:', error);\\n}\";\n return processed;\n }\n /**\n * Execute the custom script\n */\n execute(): void {\n if (!this.isInitialized || !this.customScript) {\n return;\n }\n // Prevent extremely large scripts\n if (this.customScript.length > 200000) {\n console.warn('[Custom Script] Script too large, aborting execution.');\n return;\n }\n this.cleanup();\n const processedScript = this.preprocessScript(this.customScript);\n try {\n const app = this.api.app;\n let cancelled = false;\n // Watchdog to prevent runaway update callbacks\n const watchdog = () => {\n if (cancelled)\n return;\n if (this.updateCallbacks.length > 200) {\n console.warn('[Custom Script] Too many update callbacks; further callbacks ignored.');\n cancelled = true;\n }\n else {\n requestAnimationFrame(watchdog);\n }\n };\n requestAnimationFrame(watchdog);\n // Wrapped app with tracked registerUpdate (PlayCanvas equivalent of registerBeforeRender)\n const wrappedApp = Object.create(app);\n wrappedApp.registerUpdate = (callback: () => void) => {\n if (typeof callback !== 'function' || cancelled)\n return;\n const safeCallback = () => {\n try {\n callback();\n }\n catch (err) {\n console.error('[Custom Script] Error in update callback:', err);\n }\n };\n this.updateCallbacks.push(safeCallback);\n app.on('update', safeCallback);\n this.addCleanup(() => {\n app.off('update', safeCallback);\n const idx = this.updateCallbacks.indexOf(safeCallback);\n if (idx !== -1)\n this.updateCallbacks.splice(idx, 1);\n });\n };\n // Also support BabylonJS-style API for compatibility\n wrappedApp.registerBeforeRender = wrappedApp.registerUpdate;\n // Build limited API passed to user script\n const { camera, pc: pcNamespace, canvas, getScrollPercentage, getCurrentWaypointIndex, getHotspots, getSplats, getHTMLMeshes } = this.api;\n // Build the function body\n const body = \"'use strict';\\n\" + processedScript;\n // Create the function with sandboxed parameters\n // eslint-disable-next-line no-new-func\n const func = new Function('app', 'camera', 'pc', 'canvas', 'getScrollPercentage', 'getCurrentWaypointIndex', 'getHotspots', 'getSplats', 'getHTMLMeshes', 'registerCleanup', 'registerUpdate', 'exports', 'module', 'require', 'globalThis', 'window', 'document', 'self', body);\n // Provide restricted globals (prevent direct window access by shadowing)\n const exports: Record<string, unknown> = {};\n const module: {\n exports: Record<string, unknown>;\n } = { exports };\n const fakeRequire = () => { throw new Error('require not available in custom script'); };\n const blocked = new Proxy({}, { get: () => undefined, set: () => false });\n const possibleReturn = func(wrappedApp, camera, pcNamespace, canvas, getScrollPercentage, getCurrentWaypointIndex, getHotspots, getSplats, getHTMLMeshes, (fn: () => void) => this.addCleanup(fn), wrappedApp.registerUpdate.bind(wrappedApp), exports, module, fakeRequire, blocked, undefined, undefined, undefined);\n // Check for returned cleanup function\n const cleanupCandidate = possibleReturn || module.exports || exports.default || exports.cleanup;\n if (typeof cleanupCandidate === 'function') {\n this.addCleanup(cleanupCandidate);\n }\n console.log('[Custom Script] Executed successfully');\n }\n catch (error) {\n this.lastError = error;\n console.error('[Custom Script] Execution error:', error);\n }\n }\n /**\n * Run all cleanup functions\n */\n cleanup(): void {\n this.scriptCleanup.forEach(fn => {\n try {\n fn();\n }\n catch (error) {\n console.error('[Custom Script] Cleanup error:', error);\n }\n });\n this.scriptCleanup = [];\n this.updateCallbacks = [];\n }\n /**\n * Get the last error that occurred during execution\n */\n getLastError(): unknown {\n return this.lastError;\n }\n /**\n * Dispose of the script system and cleanup\n */\n dispose(): void {\n this.cleanup();\n this.isInitialized = false;\n }\n}\n/**\n * Setup custom script system with the viewer\n */\nexport function setupCustomScript(app: pc.Application, camera: pc.Entity, canvas: HTMLCanvasElement, customScript: string, getScrollPercentage: () => number, getCurrentWaypointIndex: () => number, getHotspots: () => pc.Entity[], getSplats: () => pc.Entity[], getHTMLMeshes: () => unknown[]): CustomScriptSystem | null {\n if (!customScript || customScript.trim() === '') {\n console.log('[Custom Script] No custom script provided');\n return null;\n }\n console.log('[Custom Script] Initializing custom script system...');\n console.log('[Custom Script] Script length:', customScript.length);\n const scriptSystem = new CustomScriptSystem(customScript);\n scriptSystem.initialize({\n app,\n camera,\n pc,\n canvas,\n getScrollPercentage,\n getCurrentWaypointIndex,\n getHotspots,\n getSplats,\n getHTMLMeshes\n });\n scriptSystem.execute();\n return scriptSystem;\n}\n","/**\n * FrameSequencePlayer - 4DGS Frame-by-Frame Playback\n *\n * Handles loading and playing back sequences of PLY/SOG splat files\n * for volumetric video (4D Gaussian Splatting) playback.\n */\nimport * as pc from 'playcanvas';\nexport interface FrameSequenceConfig {\n frameUrls: string[]; // Array of PLY/SOG URLs\n fps?: number; // Playback FPS (default: 24)\n loop?: boolean; // Loop playback (default: true)\n preloadCount?: number; // Frames to preload ahead (default: 5)\n autoplay?: boolean; // Start playing immediately (default: false)\n}\nexport interface FrameSequencePlayerOptions {\n onFrameChange?: (frame: number, total: number) => void;\n onLoadProgress?: (loaded: number, total: number) => void;\n onError?: (error: string) => void;\n}\ntype FramePlayerEventCallback = (...args: unknown[]) => void;\nexport class FrameSequencePlayer {\n private app: pc.Application;\n private frameUrls: string[];\n private frameAssets: Map<number, pc.Asset> = new Map();\n private entities: [\n pc.Entity,\n pc.Entity\n ];\n private activeEntityIndex: number = 0;\n private currentFrame: number = 0;\n private fps: number;\n private isPlaying: boolean = false;\n private loop: boolean;\n private preloadCount: number;\n private lastFrameTime: number = 0;\n private frameInterval: number;\n private loadingFrames: Set<number> = new Set();\n private destroyed: boolean = false;\n private isDisplaying: boolean = false;\n private listeners: Map<string, Set<FramePlayerEventCallback>> = new Map();\n constructor(app: pc.Application, config: FrameSequenceConfig, private options: FrameSequencePlayerOptions = {}) {\n this.app = app;\n this.frameUrls = config.frameUrls;\n this.fps = config.fps || 24;\n this.loop = config.loop !== false;\n this.preloadCount = config.preloadCount || 5;\n this.frameInterval = 1000 / this.fps;\n // Create double-buffer entities\n this.entities = [\n this.createSplatEntity('frameEntityA'),\n this.createSplatEntity('frameEntityB'),\n ];\n // Hide both initially\n this.entities[0].enabled = false;\n this.entities[1].enabled = false;\n // Start preloading\n this.preloadInitialFrames();\n // Auto-play if configured\n if (config.autoplay) {\n // Wait for first frame to load before playing\n this.preloadFrame(0).then(() => {\n if (!this.destroyed) {\n this.play();\n }\n });\n }\n }\n private createSplatEntity(name: string): pc.Entity {\n const entity = new pc.Entity(name);\n entity.addComponent('gsplat', {});\n this.app.root.addChild(entity);\n return entity;\n }\n /**\n * Preload initial frames for smooth playback start\n */\n private async preloadInitialFrames(): Promise<void> {\n const framesToLoad = Math.min(this.preloadCount, this.frameUrls.length);\n const loadPromises: Promise<pc.Asset | null>[] = [];\n for (let i = 0; i < framesToLoad; i++) {\n loadPromises.push(this.preloadFrame(i));\n }\n await Promise.all(loadPromises);\n this.options.onLoadProgress?.(framesToLoad, this.frameUrls.length);\n }\n /**\n * Preload a specific frame\n */\n async preloadFrame(index: number): Promise<pc.Asset | null> {\n if (this.destroyed)\n return null;\n if (index < 0 || index >= this.frameUrls.length)\n return null;\n if (this.frameAssets.has(index))\n return this.frameAssets.get(index)!;\n if (this.loadingFrames.has(index))\n return null;\n this.loadingFrames.add(index);\n return new Promise((resolve) => {\n const url = this.frameUrls[index];\n const asset = new pc.Asset(`frame_${index}`, 'gsplat', { url });\n asset.on('load', () => {\n if (!this.destroyed) {\n this.frameAssets.set(index, asset);\n this.loadingFrames.delete(index);\n }\n resolve(asset);\n });\n asset.on('error', (err: string) => {\n console.error(`Failed to load frame ${index}:`, err);\n this.loadingFrames.delete(index);\n this.options.onError?.(`Failed to load frame ${index}: ${err}`);\n resolve(null);\n });\n this.app.assets.add(asset);\n this.app.assets.load(asset);\n });\n }\n /**\n * Unload a frame to free memory\n */\n unloadFrame(index: number): void {\n const asset = this.frameAssets.get(index);\n if (asset) {\n this.app.assets.remove(asset);\n asset.unload();\n this.frameAssets.delete(index);\n }\n }\n /**\n * Update the preload window based on current frame\n */\n private updatePreloadWindow(): void {\n // Preload upcoming frames\n for (let i = 1; i <= this.preloadCount; i++) {\n const frameIndex = this.loop\n ? (this.currentFrame + i) % this.frameUrls.length\n : this.currentFrame + i;\n if (frameIndex < this.frameUrls.length) {\n this.preloadFrame(frameIndex);\n }\n }\n // Unload old frames (keep 2 behind current)\n const keepBehind = 2;\n for (const [index] of this.frameAssets) {\n const distance = this.currentFrame - index;\n if (distance > keepBehind && distance < this.frameUrls.length - this.preloadCount) {\n this.unloadFrame(index);\n }\n }\n }\n /**\n * Display a specific frame using double-buffering\n */\n private async displayFrame(index: number): Promise<void> {\n if (this.destroyed || this.isDisplaying)\n return;\n this.isDisplaying = true;\n try {\n let asset: pc.Asset | null | undefined = this.frameAssets.get(index);\n // If frame isn't loaded yet, wait for it\n if (!asset) {\n asset = await this.preloadFrame(index);\n if (!asset || this.destroyed)\n return;\n }\n const nextEntityIdx = (this.activeEntityIndex + 1) % 2;\n const currentEntity = this.entities[this.activeEntityIndex];\n const nextEntity = this.entities[nextEntityIdx];\n // Set new asset on inactive entity\n const gsplat = nextEntity.gsplat;\n if (gsplat) {\n gsplat.asset = asset;\n }\n // Swap visibility\n nextEntity.enabled = true;\n currentEntity.enabled = false;\n this.activeEntityIndex = nextEntityIdx;\n this.currentFrame = index;\n // Emit frame change event\n this.emit('frameChange', index, this.frameUrls.length);\n this.options.onFrameChange?.(index, this.frameUrls.length);\n // Update preload window\n this.updatePreloadWindow();\n } finally {\n this.isDisplaying = false;\n }\n }\n /**\n * Main update loop - called every frame\n */\n update(dt: number): void {\n if (!this.isPlaying || this.destroyed)\n return;\n const now = performance.now();\n const elapsed = now - this.lastFrameTime;\n if (elapsed >= this.frameInterval) {\n this.lastFrameTime = now - (elapsed % this.frameInterval);\n let nextFrame = this.currentFrame + 1;\n if (nextFrame >= this.frameUrls.length) {\n if (this.loop) {\n nextFrame = 0;\n }\n else {\n this.pause();\n this.emit('complete');\n return;\n }\n }\n this.displayFrame(nextFrame);\n }\n }\n // ============================================================================\n // Public Playback API\n // ============================================================================\n play(): void {\n if (this.isPlaying || this.destroyed)\n return;\n this.isPlaying = true;\n this.lastFrameTime = performance.now();\n // Show current frame if not already visible\n if (!this.entities[this.activeEntityIndex].enabled) {\n this.displayFrame(this.currentFrame);\n }\n this.emit('play');\n }\n pause(): void {\n this.isPlaying = false;\n this.emit('pause');\n }\n stop(): void {\n this.isPlaying = false;\n this.currentFrame = 0;\n this.displayFrame(0);\n this.emit('stop');\n }\n setFrame(index: number): void {\n if (index < 0)\n index = 0;\n if (index >= this.frameUrls.length)\n index = this.frameUrls.length - 1;\n this.displayFrame(index);\n }\n nextFrame(): void {\n let next = this.currentFrame + 1;\n if (next >= this.frameUrls.length) {\n next = this.loop ? 0 : this.frameUrls.length - 1;\n }\n this.setFrame(next);\n }\n previousFrame(): void {\n let prev = this.currentFrame - 1;\n if (prev < 0) {\n prev = this.loop ? this.frameUrls.length - 1 : 0;\n }\n this.setFrame(prev);\n }\n // ============================================================================\n // Getters/Setters\n // ============================================================================\n getCurrentFrame(): number {\n return this.currentFrame;\n }\n getTotalFrames(): number {\n return this.frameUrls.length;\n }\n getProgress(): number {\n return this.frameUrls.length > 1\n ? this.currentFrame / (this.frameUrls.length - 1)\n : 0;\n }\n setProgress(progress: number): void {\n const frame = Math.round(progress * (this.frameUrls.length - 1));\n this.setFrame(frame);\n }\n getFps(): number {\n return this.fps;\n }\n setFps(fps: number): void {\n this.fps = fps;\n this.frameInterval = 1000 / fps;\n }\n getIsPlaying(): boolean {\n return this.isPlaying;\n }\n setLoop(loop: boolean): void {\n this.loop = loop;\n }\n getLoop(): boolean {\n return this.loop;\n }\n // ============================================================================\n // Transform methods (apply to both entities)\n // ============================================================================\n setPosition(x: number, y: number, z: number): void {\n this.entities[0].setPosition(x, y, z);\n this.entities[1].setPosition(x, y, z);\n }\n setRotation(x: number, y: number, z: number): void {\n this.entities[0].setEulerAngles(x, y, z);\n this.entities[1].setEulerAngles(x, y, z);\n }\n setScale(x: number, y: number, z: number): void {\n this.entities[0].setLocalScale(x, y, z);\n this.entities[1].setLocalScale(x, y, z);\n }\n // ============================================================================\n // Event Emitter\n // ============================================================================\n on(event: string, callback: FramePlayerEventCallback): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(callback);\n }\n off(event: string, callback: FramePlayerEventCallback): void {\n this.listeners.get(event)?.delete(callback);\n }\n private emit(event: string, ...args: unknown[]): void {\n this.listeners.get(event)?.forEach(cb => cb(...args));\n }\n // ============================================================================\n // Cleanup\n // ============================================================================\n destroy(): void {\n this.destroyed = true;\n this.isPlaying = false;\n // Unload all assets\n for (const [index] of this.frameAssets) {\n this.unloadFrame(index);\n }\n // Remove entities\n this.entities[0].destroy();\n this.entities[1].destroy();\n this.listeners.clear();\n }\n}\n","/**\n * Dynamic Viewer for StorySplat\n *\n * Creates an embedded PlayCanvas viewer from scene data.\n * Uses the same core logic as HTML export for 100% parity.\n */\nimport * as pc from 'playcanvas';\nimport { transformSceneToExportProps, exportPropsToViewerConfig } from '../transformers/sceneToConfig';\nimport type { SceneData, ViewerInstance, EditorViewerInstance, ViewerOptions, ViewerEvent, HotspotData, LightConfig, ParticleConfig, CustomMeshConfig, WaypointData, PortalData, CollisionMeshConfig, AudioEmitterConfig, ButtonLabels } from '../types';\nimport { createUIElements, connectUIToViewer, injectStyles, hidePreloader, updatePreloaderProgress, showHotspotPopup, setJoystickVisible, updateJoystickPosition, updateLookZoneState, createLazyLoadUI, updateWaypointListActive, setupWaypointListClickHandlers, showErrorPopup, getButtonLabel, DEFAULT_BUTTON_LABELS, updateFpsCounter, type UIElements } from './viewerUI';\nimport { CameraControls } from './CameraControls';\nimport { CharacterController } from './CharacterController';\nimport { getGsplatRevealRadialClass, getRevealPreset, type RevealPreset } from '../effects';\nimport { AnimatedGifTexture, isGifUrl } from './AnimatedGifHelper';\nimport { setupHtmlMeshes, type HtmlMeshConfig, type HtmlMeshManager } from './HtmlMeshHelper';\nimport { setupCustomScript, CustomScriptSystem } from './CustomScriptSystem';\nimport { FrameSequencePlayer } from './FrameSequencePlayer';\ninterface WindowWithLegacyAudio extends Window {\n opera?: string;\n webkitAudioContext?: typeof AudioContext;\n}\ninterface AudioSlotWithStoredVolume extends pc.SoundSlot {\n _storedVolume?: number;\n}\ninterface ViewerRuntimeApp extends pc.Application {\n __htmlMeshManager?: HtmlMeshManager;\n __customScriptSystem?: CustomScriptSystem;\n}\ninterface ViewerRuntimeEntity extends pc.Entity {\n modelEntity?: pc.Entity;\n _billboardActive?: boolean;\n _billboardOriginalRotation?: pc.Vec3;\n visibilityRange?: {\n type: 'percentage' | 'waypoint';\n start: number;\n end: number;\n };\n opacityConfig?: {\n mode: string;\n value: number;\n };\n shouldBeVisible?: boolean;\n hiddenUntilTextureLoaded?: boolean;\n textureLoaded?: boolean;\n targetOpacity?: number;\n hotspotMaterial?: pc.StandardMaterial;\n portalMaterial?: pc.StandardMaterial;\n videoElement?: HTMLVideoElement;\n alphaVideoElement?: HTMLVideoElement | null;\n mediaTriggerMode?: string;\n proximityDistance?: number;\n isVideoPlaying?: boolean;\n videoSpatialAudio?: {\n audioCtx?: AudioContext;\n } | null;\n audioElements?: {\n audio: HTMLAudioElement;\n audioCtx?: AudioContext;\n source?: MediaElementAudioSourceNode;\n panner?: PannerNode;\n updateAudioPosition?: () => void;\n };\n hotspotData?: HotspotData;\n portalData?: PortalData;\n meshClickHandler?: (event: MouseEvent) => void;\n meshHoverHandler?: (event: MouseEvent) => void;\n meshLeaveHandler?: () => void;\n _collisionMeshId?: string;\n wasInProximity?: boolean;\n proximityTriggered?: boolean;\n gifCanvas?: HTMLCanvasElement;\n gifTexture?: pc.Texture;\n gifAnimationFrame?: number;\n videoOverlay?: pc.Entity;\n}\ninterface UIElementsWithPortalPopup extends UIElements {\n portalPopup?: HTMLElement;\n showHotspotPopup?: (hotspot: HotspotData) => void;\n}\ninterface LegacyVec3 {\n x?: number;\n y?: number;\n z?: number;\n _x?: number;\n _y?: number;\n _z?: number;\n}\ninterface LegacyQuat extends LegacyVec3 {\n w?: number;\n _w?: number;\n}\ninterface LegacyColor {\n r?: number;\n g?: number;\n b?: number;\n a?: number;\n}\ninterface LegacyParticleConfig extends ParticleConfig {\n emitterPosition?: LegacyVec3;\n gravity?: LegacyVec3;\n color1?: LegacyColor;\n color2?: LegacyColor;\n colorDead?: LegacyColor;\n direction1?: LegacyVec3;\n direction2?: LegacyVec3;\n minLifeTime?: number;\n maxLifeTime?: number;\n emitterType?: string;\n emitBoxMin?: LegacyVec3;\n emitBoxMax?: LegacyVec3;\n blendMode?: string;\n minEmitPower?: number;\n maxEmitPower?: number;\n minSize?: number;\n maxSize?: number;\n minAngularSpeed?: number;\n maxAngularSpeed?: number;\n angularSpeed?: number;\n minInitialRotation?: number;\n maxInitialRotation?: number;\n minScaleX?: number;\n maxScaleX?: number;\n minScaleY?: number;\n maxScaleY?: number;\n emitRate?: number;\n depthWrite?: boolean;\n softParticles?: number;\n halfLambert?: boolean;\n alignToMotion?: boolean;\n stretch?: number;\n autoPlay?: boolean;\n orientation?: number;\n localSpace?: boolean;\n colorGradients?: Array<{\n gradient: number;\n color: LegacyColor;\n }>;\n}\ninterface LegacyWaypointAudioConfig {\n loop?: boolean;\n volume?: number;\n spatialSound?: boolean;\n distanceModel?: string;\n maxDistance?: number;\n refDistance?: number;\n rolloffFactor?: number;\n autoplay?: boolean;\n stopOnExit?: boolean;\n url?: string;\n}\ninterface SceneWithToneMapping extends pc.Scene {\n toneMapping?: number;\n}\n/** Typed reveal effect script with specific properties from GsplatRevealRadial */\ninterface RevealEffectScript extends pc.ScriptType {\n center: pc.Vec3;\n speed: number;\n acceleration: number;\n delay: number;\n oscillationIntensity: number;\n dotTint: pc.Color;\n waveTint: pc.Color;\n endRadius: number;\n}\ntype LightConfigLike = Omit<LightConfig, 'position' | 'rotation' | 'direction'> & {\n position?: LegacyVec3;\n rotation?: LegacyVec3;\n direction?: LegacyVec3;\n enabled?: boolean;\n angle?: number;\n exponent?: number;\n innerAngle?: number;\n outerAngle?: number;\n shadowBias?: number;\n normalOffsetBias?: number;\n};\ntype WaypointConfigLike = Omit<WaypointData, 'position' | 'interactions'> & {\n position?: LegacyVec3;\n interactions?: Array<{\n id?: string;\n type?: string;\n data?: LegacyWaypointAudioConfig;\n }>;\n};\ntype CustomMeshConfigLike = Omit<CustomMeshConfig, 'position' | 'rotation' | 'scale' | 'interaction'> & {\n position?: LegacyVec3;\n rotation?: LegacyVec3;\n scale?: LegacyVec3;\n enabled?: boolean;\n interaction?: (CustomMeshConfig['interaction'] & {\n popupContent?: string;\n }) | undefined;\n};\ntype HotspotConfigLike = Omit<HotspotData, 'position' | 'rotation' | 'scale' | 'id' | 'type' | 'data' | 'activationMode'> & {\n position?: LegacyVec3;\n rotation?: LegacyVec3;\n scale?: number | LegacyVec3;\n id?: string;\n type?: string;\n data?: unknown;\n activationMode?: string;\n enabled?: boolean;\n videoSpatialAudio?: boolean;\n videoDistanceModel?: DistanceModelType;\n videoRefDistance?: number;\n videoMaxDistance?: number;\n videoRolloffFactor?: number;\n};\ntype PortalConfigLike = Omit<PortalData, 'position' | 'rotation' | 'scale' | 'id' | 'type' | 'targetSceneId' | 'activationMode'> & {\n position?: LegacyVec3;\n rotation?: LegacyVec3;\n scale?: number | LegacyVec3;\n id?: string;\n type?: string;\n targetSceneId?: string;\n activationMode?: string;\n enabled?: boolean;\n useLighting?: boolean;\n proximityDistance?: number;\n confirmNavigation?: boolean;\n};\ntype GSplatComponentWithRuntime = Omit<pc.GSplatComponent, '_instance'> & {\n unified?: boolean;\n instance?: pc.GSplatInstance;\n _instance?: pc.GSplatInstance;\n lodDistances?: number[];\n};\ntype ScriptComponentWithCreate = Omit<pc.ScriptComponent, 'create'> & {\n create?: (scriptType: typeof pc.ScriptType | string) => pc.ScriptType;\n};\ninterface GsplatHandlerWithOctreeParser {\n _octreeParser?: {\n load: (urlObj: {\n load: string;\n original?: string;\n }, callback: (err: unknown, resource: unknown) => void) => void;\n };\n}\ninterface GsplatHandlerWithParsers {\n parsers?: {\n octree?: {\n load: (urlObj: {\n load: string;\n original?: string;\n }, callback: (err: unknown, resource: unknown) => void) => void;\n };\n };\n}\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype EventCallback = (...args: any[]) => void;\n// Event emitter for viewer events\nclass EventEmitter {\n private listeners: Map<string, Set<EventCallback>> = new Map();\n on(event: string, callback: EventCallback): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(callback);\n }\n off(event: string, callback: EventCallback): void {\n this.listeners.get(event)?.delete(callback);\n }\n emit(event: string, ...args: unknown[]): void {\n this.listeners.get(event)?.forEach(cb => cb(...args));\n }\n}\n// Mobile detection utility\nfunction isMobileDevice(): boolean {\n // Only check user agent - don't use maxTouchPoints as many laptops have touch\n const userAgent = navigator.userAgent || navigator.vendor || (window as WindowWithLegacyAudio).opera || '';\n return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);\n}\n// LOD (Level of Detail) presets for different device capabilities\n// Based on official PlayCanvas GSplat LOD streaming example\nconst LOD_PRESETS = {\n 'desktop-max': {\n range: [0, 5] as [\n number,\n number\n ],\n lodDistances: [15, 30, 80, 250, 300]\n },\n 'desktop': {\n range: [0, 2] as [\n number,\n number\n ],\n lodDistances: [15, 30, 80, 250, 300]\n },\n 'mobile-max': {\n range: [1, 2] as [\n number,\n number\n ],\n lodDistances: [15, 30, 80, 250, 300]\n },\n 'mobile': {\n range: [2, 5] as [\n number,\n number\n ],\n lodDistances: [15, 30, 80, 250, 300]\n }\n} as const;\ntype LodPresetName = keyof typeof LOD_PRESETS;\n// Helper to detect if URL points to LOD streaming format\nfunction isLodStreamingFormat(url: string): boolean {\n const isLod = url.includes('lod-meta.json');\n if (isLod) {\n console.log('[SPLAT] Detected LOD streaming format (lod-meta.json)');\n }\n return isLod;\n}\n// Helper to detect SOG format\nfunction isSogFormat(url: string): boolean {\n return url.includes('.sog') || url.includes('lod-meta.json');\n}\n/**\n * Create an embedded viewer from scene data\n */\nexport function createViewer(container: HTMLElement, scene: SceneData, options: ViewerOptions = {}): ViewerInstance {\n console.log('[StorySplat Viewer] BUILD 2025-01-22-orbit-fly-v3');\n // Handle lazy loading - show thumbnail with start button before loading viewer\n if (options.lazyLoad) {\n const lazyEvents = new EventEmitter();\n let actualInstance: ViewerInstance | null = null;\n let pendingButtonLabels: Partial<ButtonLabels> | undefined;\n // Determine thumbnail URL and button text\n const thumbnailUrl = options.lazyLoadThumbnail || scene.uiOptions?.lazyLoadThumbnailUrl || scene.thumbnailUrl;\n // Priority: explicit option > scene lazyLoadButtonText > scene buttonLabels.startExperience > default\n const buttonText = options.lazyLoadButtonText\n || scene.uiOptions?.lazyLoadButtonText\n || getButtonLabel(scene.uiOptions?.buttonLabels, 'startExperience');\n const uiColor = scene.uiColor || '#4CAF50';\n console.log('[StorySplat Viewer] Lazy loading enabled, showing start button');\n // Create lazy load UI (supports image, video, and GIF thumbnails)\n createLazyLoadUI(container, {\n thumbnailUrl,\n thumbnailType: options.lazyLoadThumbnailType || scene.uiOptions?.lazyLoadThumbnailType,\n buttonText,\n uiColor,\n onStart: () => {\n console.log('[StorySplat Viewer] User clicked start, initializing viewer...');\n // Create actual viewer without lazy loading\n actualInstance = createViewer(container, scene, { ...options, lazyLoad: false });\n // Apply any pending button labels that were set before the instance was created\n if (pendingButtonLabels) {\n actualInstance.setButtonLabels(pendingButtonLabels);\n pendingButtonLabels = undefined;\n }\n // Proxy events from actual instance to lazy instance\n actualInstance.on('ready', () => lazyEvents.emit('ready'));\n actualInstance.on('error', (err) => lazyEvents.emit('error', err));\n actualInstance.on('waypointChange', (data) => lazyEvents.emit('waypointChange', data));\n actualInstance.on('playbackStart', () => lazyEvents.emit('playbackStart'));\n actualInstance.on('playbackStop', () => lazyEvents.emit('playbackStop'));\n actualInstance.on('loaded', () => lazyEvents.emit('loaded'));\n actualInstance.on('progress', (data) => lazyEvents.emit('progress', data));\n }\n });\n // Return deferred ViewerInstance - methods delegate to actual instance when available\n const deferredInstance: ViewerInstance = {\n app: null as unknown as pc.Application,\n canvas: null as unknown as HTMLCanvasElement,\n goToWaypoint: (index) => actualInstance?.goToWaypoint(index),\n nextWaypoint: () => actualInstance?.nextWaypoint(),\n prevWaypoint: () => actualInstance?.prevWaypoint(),\n getCurrentWaypointIndex: () => actualInstance?.getCurrentWaypointIndex() ?? 0,\n getWaypointCount: () => actualInstance?.getWaypointCount() ?? 0,\n setPosition: (x, y, z) => actualInstance?.setPosition(x, y, z),\n setRotation: (x, y, z) => actualInstance?.setRotation(x, y, z),\n getPosition: () => actualInstance?.getPosition() ?? { x: 0, y: 0, z: 0 },\n getRotation: () => actualInstance?.getRotation() ?? { x: 0, y: 0, z: 0 },\n play: () => actualInstance?.play(),\n pause: () => actualInstance?.pause(),\n stop: () => actualInstance?.stop(),\n isPlaying: () => actualInstance?.isPlaying() ?? false,\n // Mode\n setCameraMode: (mode) => actualInstance?.setCameraMode(mode),\n getCameraMode: () => actualInstance?.getCameraMode() ?? 'tour',\n setExploreMode: (mode) => actualInstance?.setExploreMode(mode),\n // Splat API\n goToOriginalSplat: () => actualInstance?.goToOriginalSplat(),\n goToSplat: async (url) => actualInstance?.goToSplat(url),\n getCurrentSplatUrl: () => actualInstance?.getCurrentSplatUrl() ?? '',\n isShowingOriginalSplat: () => actualInstance?.isShowingOriginalSplat() ?? true,\n getAdditionalSplats: () => actualInstance?.getAdditionalSplats() ?? [],\n // Progress\n setProgress: (progress) => actualInstance?.setProgress(progress),\n getProgress: () => actualInstance?.getProgress() ?? 0,\n // Audio\n muteAll: () => actualInstance?.muteAll(),\n unmuteAll: () => actualInstance?.unmuteAll(),\n isMuted: () => actualInstance?.isMuted() ?? false,\n // Hotspot interaction\n getHotspots: () => actualInstance?.getHotspots() ?? [],\n triggerHotspot: (id) => actualInstance?.triggerHotspot(id),\n closeHotspot: () => actualInstance?.closeHotspot(),\n destroy: () => {\n if (actualInstance) {\n actualInstance.destroy();\n }\n else {\n // Clean up lazy load UI if viewer wasn't started\n container.querySelectorAll('.storysplat-lazy-load-container, .storysplat-viewer-container').forEach(el => el.remove());\n container.classList.remove('storysplat-viewer-container');\n }\n },\n resize: () => actualInstance?.resize(),\n navigateToScene: async (sceneId) => {\n if (actualInstance) {\n return actualInstance.navigateToScene(sceneId);\n }\n },\n setButtonLabels: (labels) => {\n if (actualInstance) {\n actualInstance.setButtonLabels(labels);\n }\n else {\n pendingButtonLabels = { ...pendingButtonLabels, ...labels };\n }\n },\n on: (event, callback) => lazyEvents.on(event, callback),\n off: (event, callback) => lazyEvents.off(event, callback)\n };\n return deferredInstance;\n }\n const events = new EventEmitter();\n // Style isolation: reset inherited styles from parent page unless opted out\n if (!options.allowParentStyles) {\n const originalWidth = container.style.width;\n const originalHeight = container.style.height;\n container.style.all = 'initial';\n container.style.position = 'relative';\n container.style.display = 'block';\n container.style.width = originalWidth || '100%';\n container.style.height = originalHeight || '100%';\n container.style.overflow = 'hidden';\n container.style.fontFamily = 'system-ui, -apple-system, sans-serif';\n }\n // Set up internal error handler to show user-friendly error popup\n events.on('error', (err: Error) => {\n console.error('[StorySplat Viewer] Error event:', err.message);\n showErrorPopup(container, err.message);\n });\n const config = exportPropsToViewerConfig(transformSceneToExportProps(scene));\n console.log('[StorySplat Viewer] Creating viewer with config:', config);\n console.log('[StorySplat Viewer] Scale config:', config.scale);\n console.log('[StorySplat Viewer] Raw scene data:', { splatScale: scene.splatScale, scale: scene.scale });\n // Extract UI options from config (respecting JSON settings)\n const isEditorMode = options.editor === true;\n const showUI = isEditorMode ? (options.editorSkipUI ? false : options.showUI !== false) : (options.showUI !== false);\n const uiColor = config.uiColor || '#4CAF50';\n const uiOpts = config.uiOptions || {};\n // Resolve template type: explicit option > scene uiOptions > default minimal\n const templateType = options.template || uiOpts.uiType || 'minimal';\n // Mutable button labels for runtime updates via setButtonLabels()\n let currentButtonLabels: ButtonLabels | undefined = uiOpts.buttonLabels;\n // Map camera modes from export format to UI format\n const mapCameraMode = (mode: string) => {\n if (mode === 'first-person')\n return 'tour';\n if (mode === 'drone')\n return 'explore';\n return mode;\n };\n // Check if collision meshes are available for walk mode\n const hasCollisionMeshesForWalk = config.collisionMeshesData && config.collisionMeshesData.length > 0;\n let allowedModes = (config.allowedCameraModes || ['orbit', 'first-person', 'drone'])\n .map(mapCameraMode)\n .filter((v, i, a) => a.indexOf(v) === i); // unique\n // Add 'walk' mode if collision meshes are available and it's in the allowed modes list\n // OR automatically add it if collision meshes exist (for backwards compatibility)\n if (hasCollisionMeshesForWalk && !allowedModes.includes('walk')) {\n allowedModes.push('walk');\n }\n // Remove 'walk' if no collision meshes (can't walk without collisions)\n if (!hasCollisionMeshesForWalk && allowedModes.includes('walk')) {\n allowedModes = allowedModes.filter(m => m !== 'walk');\n }\n const defaultMode = mapCameraMode(config.defaultCameraMode || 'orbit');\n // Detect if scene has any audio sources (waypoint audio, hotspot audio, audio emitters)\n const sceneHasAudio = !!(\n config.audioEmitters?.length ||\n config.hotspots?.some(h => h.audioUrl) ||\n config.hotspots?.some(h => h.type === 'video' && h.videoMuted !== true) ||\n config.waypoints?.some(w => w.interactions?.some(i => i.type === 'audio'))\n );\n let uiElements: UIElements = {};\n // Create UI elements FIRST (to show preloader while loading)\n if (showUI) {\n uiElements = createUIElements(container, config, {\n uiColor,\n showScrollControls: config.waypoints && config.waypoints.length > 0,\n showModeToggle: allowedModes.length > 1,\n showFullscreenButton: !uiOpts.hideFullscreenButton,\n showHelpButton: !uiOpts.hideHelpButton && !uiOpts.hideInfoButton,\n showMuteButton: !uiOpts.hideMuteButton && sceneHasAudio,\n showPreloader: true,\n allowedCameraModes: allowedModes,\n defaultCameraMode: defaultMode,\n buttonLabels: currentButtonLabels,\n // Whitelabeling options for preloader\n customPreloaderLogoUrl: uiOpts.customPreloaderLogoUrl,\n // Whitelabeling options for watermark\n hideWatermark: uiOpts.hideWatermark,\n watermarkText: uiOpts.watermarkText,\n watermarkLink: uiOpts.watermarkLink,\n sceneId: scene.sceneId,\n // Waypoint list dropdown\n showWaypointList: uiOpts.showWaypointList,\n // Layout template\n template: templateType,\n // Debug mode (FPS counter)\n debugMode: uiOpts.debugMode\n });\n }\n // Create canvas\n const canvas = document.createElement('canvas');\n canvas.id = 'storysplat-viewer-canvas';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n canvas.style.display = 'block';\n container.appendChild(canvas);\n // Initialize PlayCanvas with WebGL fallback chain\n // Try WebGL2 first, then WebGL1 if that fails\n let app: pc.Application;\n // Graphics device options with fallback support\n const baseGraphicsOptions = {\n antialias: false,\n alpha: false,\n powerPreference: 'high-performance' as const\n };\n try {\n // First try with default settings (WebGL2 preferred)\n app = new pc.Application(canvas, {\n graphicsDeviceOptions: baseGraphicsOptions,\n mouse: new pc.Mouse(canvas),\n touch: new pc.TouchDevice(canvas),\n keyboard: new pc.Keyboard(window)\n });\n console.log('[StorySplat Viewer] Graphics initialized successfully');\n }\n catch (webgl2Error) {\n console.warn('[StorySplat Viewer] WebGL2 initialization failed, trying WebGL1 fallback:', webgl2Error);\n try {\n // Fallback to WebGL1\n app = new pc.Application(canvas, {\n graphicsDeviceOptions: {\n ...baseGraphicsOptions,\n preferWebGl2: false\n },\n mouse: new pc.Mouse(canvas),\n touch: new pc.TouchDevice(canvas),\n keyboard: new pc.Keyboard(window)\n });\n console.log('[StorySplat Viewer] WebGL1 fallback successful');\n }\n catch (webgl1Error) {\n console.error('[StorySplat Viewer] WebGL initialization failed completely:', webgl1Error);\n // Show user-friendly error message using safe DOM methods\n const errorDiv = document.createElement('div');\n errorDiv.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;background:rgba(0,0,0,0.8);padding:20px;border-radius:10px;text-align:center;font-family:sans-serif;';\n const heading = document.createElement('h3');\n heading.style.cssText = 'margin:0 0 10px 0;';\n heading.textContent = getButtonLabel(currentButtonLabels, 'errorWebGLTitle');\n const message = document.createElement('p');\n message.style.cssText = 'margin:0;';\n message.textContent = getButtonLabel(currentButtonLabels, 'errorWebGLMessage');\n errorDiv.appendChild(heading);\n errorDiv.appendChild(message);\n container.appendChild(errorDiv);\n throw new Error('WebGL initialization failed - browser may not support WebGL');\n }\n }\n // Handle WebGL context loss\n canvas.addEventListener('webglcontextlost', (e) => {\n e.preventDefault();\n console.error('[StorySplat Viewer] WebGL context lost');\n events.emit('error', new Error('WebGL context lost'));\n }, false);\n canvas.addEventListener('webglcontextrestored', () => {\n console.log('[StorySplat Viewer] WebGL context restored');\n // PlayCanvas should handle context restoration automatically\n }, false);\n app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);\n app.setCanvasResolution(pc.RESOLUTION_AUTO);\n // Start the app first (before adding entities)\n app.start();\n console.log('[StorySplat Viewer] App started');\n // Patch Layer.splitLights to guard against lights with undefined _type.\n // PlayCanvas addComponent('light') maps string type → numeric _type via an internal lookup.\n // If a light ends up with _type outside [0,1,2] (e.g. from a race during destroy),\n // the unpatched getter crashes: splitLights[light._type].push(light).\n try {\n const worldLayer = app.scene.layers.getLayerByName('World');\n const LayerProto = worldLayer && Object.getPrototypeOf(worldLayer);\n if (LayerProto && !LayerProto._splitLightsPatched) {\n const origDesc = Object.getOwnPropertyDescriptor(LayerProto, 'splitLights');\n if (origDesc?.get) {\n const origGetter = origDesc.get;\n Object.defineProperty(LayerProto, 'splitLights', {\n get() {\n // Filter out any lights with invalid _type before the getter runs\n if (this._splitLightsDirty) {\n this._lights = this._lights.filter((l: { _type?: number }) =>\n l._type !== undefined && l._type >= 0 && l._type <= 2\n );\n }\n return origGetter.call(this);\n },\n configurable: true\n });\n LayerProto._splitLightsPatched = true;\n }\n }\n } catch (e) {\n console.warn('[StorySplat Viewer] Could not patch Layer.splitLights:', e);\n }\n // Configure GSplat LOD system for optimal performance\n // This improves rendering for all splat formats (PLY, SOG, LOD)\n const isMobile = isMobileDevice();\n const lodPresetName: LodPresetName = isMobile ? 'mobile' : 'desktop';\n const lodPreset = LOD_PRESETS[lodPresetName];\n console.log('[SPLAT] Initializing LOD system for device:', isMobile ? 'mobile' : 'desktop');\n // Scene-level GSplat settings (applies to all gsplat components)\n if (app.scene.gsplat) {\n // LOD update settings - controls how frequently LOD is recalculated\n app.scene.gsplat.lodUpdateAngle = 90; // Angle change threshold for LOD update\n app.scene.gsplat.lodBehindPenalty = 2; // Penalty multiplier for splats behind camera\n app.scene.gsplat.radialSorting = true; // Enable radial sorting for better quality\n app.scene.gsplat.lodUpdateDistance = 1; // Distance change threshold for LOD update\n app.scene.gsplat.lodUnderfillLimit = 10; // Limit for underfill detection\n // LOD range settings - controls which LOD levels are used\n app.scene.gsplat.lodRangeMin = lodPreset.range[0];\n app.scene.gsplat.lodRangeMax = lodPreset.range[1];\n // SH (Spherical Harmonics) update settings for better color accuracy\n app.scene.gsplat.colorUpdateDistance = 1;\n app.scene.gsplat.colorUpdateAngle = 4;\n app.scene.gsplat.colorUpdateDistanceLodScale = 2;\n app.scene.gsplat.colorUpdateAngleLodScale = 2;\n console.log('[SPLAT] LOD system configured:', {\n preset: lodPresetName,\n lodRangeMin: lodPreset.range[0],\n lodRangeMax: lodPreset.range[1],\n lodDistances: lodPreset.lodDistances,\n lodUpdateAngle: 90,\n lodUpdateDistance: 1,\n isMobile\n });\n }\n else {\n console.warn('[SPLAT] GSplat scene settings not available - LOD may not work optimally');\n }\n // State\n let currentWaypointIndex = 0;\n let isPlaying = false;\n let splatEntity: pc.Entity | null = null;\n let revealScript: RevealEffectScript | null = null; // Reference to reveal effect script\n let isDestroyed = false; // Flag to prevent async operations after destroy\n let frameSequencePlayer: FrameSequencePlayer | null = null; // 4DGS frame sequence player\n // SplatSwap system state\n const ORIGINAL_SPLAT_URL = \"__ORIGINAL__\";\n const additionalSplats = config.additionalSplats || [];\n const keepMeshesInMemory = config.keepMeshesInMemory ?? false;\n const initialSplatExploreMode = config.initialSplatExploreMode || 'fly'; // Default to fly (POV)\n let currentSplatUrl: string | null = null;\n let isLoadingSplat = false;\n let initialSplatLoadDone = false;\n const preloadedSplats = new Map<string, pc.Entity>(); // Map URL -> splat entity\n let lastSplatCheckProgress = -1;\n let lastSplatCheckWaypointIndex = -1;\n // Create camera (matching HTML export CONFIG values)\n const camera = new pc.Entity('camera');\n // Parse background color from config (supports hex strings like \"#1a1a1a\")\n // Default to dark gray (0.1, 0.1, 0.1) if not specified\n let clearColor = new pc.Color(0.1, 0.1, 0.1);\n if (config.backgroundColor) {\n // Parse hex color string to RGB\n const hex = config.backgroundColor.replace('#', '');\n if (hex.length === 6) {\n const r = parseInt(hex.substring(0, 2), 16) / 255;\n const g = parseInt(hex.substring(2, 4), 16) / 255;\n const b = parseInt(hex.substring(4, 6), 16) / 255;\n clearColor = new pc.Color(r, g, b);\n console.log('[StorySplat Viewer] Background color set from config:', config.backgroundColor);\n }\n }\n camera.addComponent('camera', {\n clearColor,\n fov: config.fov || 60,\n nearClip: config.nearClip || 0.1,\n farClip: config.farClip || 1000\n });\n // Add audio listener for spatial audio (required for positional sound)\n camera.addComponent('audiolistener');\n console.log('[StorySplat Viewer] Camera settings:', {\n fov: config.fov,\n nearClip: config.nearClip,\n farClip: config.farClip,\n playerHeight: config.playerHeight\n });\n // Set initial camera position - matching HTML export behavior\n // If waypoints exist, set camera DIRECTLY to first waypoint to avoid 1-frame flash\n const playerHeight = config.playerHeight || 1.6;\n if (config.waypoints && config.waypoints.length > 0) {\n const wp = config.waypoints[0];\n console.log('[StorySplat Viewer] First waypoint raw:', wp);\n // Set position SYNCHRONOUSLY to avoid camera flash\n // Convert BabylonJS (left-handed) to PlayCanvas (right-handed) - negate Z\n if (wp.position) {\n const pos = wp.position;\n camera.setPosition(pos.x, pos.y, -pos.z);\n console.log('[StorySplat Viewer] Camera position (Z negated):', { x: pos.x, y: pos.y, z: -pos.z });\n }\n else {\n camera.setPosition(0, playerHeight, 5);\n }\n // Set rotation SYNCHRONOUSLY\n if (wp.rotation) {\n const finalRot = convertWaypointRotation(wp.rotation);\n camera.setRotation(finalRot);\n console.log('[StorySplat Viewer] Camera rotation set from waypoint');\n }\n }\n else {\n // No waypoints - use default position\n camera.setPosition(0, playerHeight, 5);\n camera.lookAt(new pc.Vec3(0, 0, 0));\n console.log('[StorySplat Viewer] Camera set to default position (0, playerHeight, 5)');\n }\n app.root.addChild(camera);\n // Add directional light (matching HTML export)\n // Note: PlayCanvas addComponent('light') expects string types ('directional', 'point', 'spot'),\n // NOT numeric constants (pc.LIGHTTYPE_*). The numeric constants are for internal Light class use.\n const light = new pc.Entity('light');\n light.addComponent('light', {\n type: 'directional',\n color: new pc.Color(1, 1, 1),\n intensity: 1,\n castShadows: false\n });\n light.setEulerAngles(45, 45, 0);\n app.root.addChild(light);\n console.log('[StorySplat Viewer] Light added');\n // Create CameraControls for explore mode (orbit + fly + focus)\n // Movement and rotation speeds tuned to match BabylonJS editor behavior\n // Damping values lowered for more responsive BabylonJS-like feel (0.75 vs previous 0.9)\n // Note: cameraRotationSensitivity is stored as BabylonJS angularSensibility (friction scale: 1000-100000, default 4000)\n // We convert it to PlayCanvas rotateSpeed by inverting: lower friction = higher sensitivity\n // BabylonJS uses much smaller speed values by default (e.g. 0.2).\n // Scale up for PlayCanvas so default scenes don't feel sluggish.\n const baseMoveSpeed = (config.cameraMovementSpeed || 1) * 5;\n const cameraControls = new CameraControls(camera, app, {\n moveSpeed: baseMoveSpeed,\n moveFastSpeed: baseMoveSpeed * 2.5,\n moveSlowSpeed: baseMoveSpeed * 0.5,\n rotateSpeed: 800 / (config.cameraRotationSensitivity || 4000),\n enableOrbit: true,\n enableFly: true,\n enablePan: true,\n invertRotation: config.invertCameraRotation,\n // Damping controls camera smoothing - hardcoded to 0.75 to match BabylonJS responsive feel\n // Lower = more responsive, higher = more smooth/floaty\n moveDamping: 0.75,\n rotateDamping: 0.75,\n zoomDamping: 0.8\n });\n // Create CharacterController for walk mode (first-person with collisions)\n let characterController: CharacterController | null = null;\n const hasCollisionMeshes = config.collisionMeshesData && config.collisionMeshesData.length > 0;\n if (hasCollisionMeshes) {\n // CharacterController for walk mode - tuned for BabylonJS parity\n // Uses same conversions as CameraControls for consistent feel\n characterController = new CharacterController(camera, app, {\n moveSpeed: baseMoveSpeed,\n sprintMultiplier: 2,\n lookSensitivity: (1 / (config.cameraRotationSensitivity || 4000)) * 10,\n playerHeight: config.playerHeight || 1.6,\n gravity: 20,\n jumpVelocity: 8,\n collisionRadius: 0.3,\n stepHeight: 0.3\n });\n // Load collision meshes asynchronously\n characterController.createCollisionMeshes(config.collisionMeshesData!).then(() => {\n console.log('[StorySplat Viewer] Collision meshes loaded for walk mode');\n // Share collision entities with CameraControls for explore mode (fly) collision\n cameraControls.setCollisionEntities(characterController!.collisionMeshEntities);\n }).catch((err) => {\n console.error('[StorySplat Viewer] Failed to load collision meshes:', err);\n });\n }\n // Track current camera mode\n let currentCameraMode = defaultMode;\n let waypointControlEnabled = true; // When false, waypoint navigation doesn't control camera\n // Per-waypoint orbit mode state (for tour mode with orbit waypoints)\n let currentWaypointOrbitEnabled = false;\n let currentWaypointOrbitTarget: pc.Vec3 | null = null;\n // Disable controls initially if in tour mode\n if (defaultMode === 'tour') {\n cameraControls.disable();\n }\n // Create picker for GSplat picking (double-click to focus)\n // Enable depth=true for getWorldPointAsync to work with splats (requires PlayCanvas 2.14+)\n const picker = new pc.Picker(app, 1, 1, true);\n // XR (VR/AR) Support\n let isInXR = false;\n let xrSessionType: 'vr' | 'ar' | null = null;\n // Setup XR if enabled in config\n function setupXR(): void {\n if (!config.includeXR)\n return;\n // Check if XR is supported\n if (!app.xr) {\n console.warn('[StorySplat Viewer] WebXR not supported in this browser');\n return;\n }\n const xr = app.xr; // Non-null reference for TypeScript\n const xrMode = config.xrMode || 'both';\n // Check VR availability\n if (xrMode === 'vr' || xrMode === 'both') {\n if (xr.isAvailable(pc.XRTYPE_VR)) {\n uiElements.vrButton?.classList.add('available');\n console.log('[StorySplat Viewer] VR is available');\n }\n // Listen for VR availability changes\n xr.on('available:' + pc.XRTYPE_VR, (available: boolean) => {\n if (available) {\n uiElements.vrButton?.classList.add('available');\n }\n else {\n uiElements.vrButton?.classList.remove('available');\n }\n });\n }\n // Check AR availability\n if (xrMode === 'ar' || xrMode === 'both') {\n if (xr.isAvailable(pc.XRTYPE_AR)) {\n uiElements.arButton?.classList.add('available');\n console.log('[StorySplat Viewer] AR is available');\n }\n // Listen for AR availability changes\n xr.on('available:' + pc.XRTYPE_AR, (available: boolean) => {\n if (available) {\n uiElements.arButton?.classList.add('available');\n }\n else {\n uiElements.arButton?.classList.remove('available');\n }\n });\n }\n // Handle XR session start\n xr.on('start', () => {\n isInXR = true;\n console.log('[StorySplat Viewer] XR session started');\n // Update button states\n if (xrSessionType === 'vr') {\n uiElements.vrButton?.classList.add('active');\n uiElements.vrButton!.textContent = getButtonLabel(currentButtonLabels, 'exitVr');\n }\n else if (xrSessionType === 'ar') {\n uiElements.arButton?.classList.add('active');\n uiElements.arButton!.textContent = getButtonLabel(currentButtonLabels, 'exitAr');\n }\n // Disable regular camera controls in XR\n cameraControls.disable();\n if (characterController) {\n characterController.disable();\n }\n events.emit('xrStart', { type: xrSessionType });\n });\n // Handle XR session end\n xr.on('end', () => {\n isInXR = false;\n console.log('[StorySplat Viewer] XR session ended');\n // Hide AR content panel when exiting XR\n hideARContent();\n // Reset button states\n uiElements.vrButton?.classList.remove('active');\n uiElements.arButton?.classList.remove('active');\n if (uiElements.vrButton)\n uiElements.vrButton.textContent = getButtonLabel(currentButtonLabels, 'vr');\n if (uiElements.arButton)\n uiElements.arButton.textContent = getButtonLabel(currentButtonLabels, 'ar');\n xrSessionType = null;\n // Re-enable camera controls based on current mode\n if (currentCameraMode === 'explore') {\n cameraControls.enable();\n }\n else if (currentCameraMode === 'walk' && characterController) {\n characterController.enable();\n }\n events.emit('xrEnd', {});\n });\n // VR button click handler\n if (uiElements.vrButton) {\n uiElements.vrButton.addEventListener('click', () => {\n if (isInXR && xrSessionType === 'vr') {\n // Exit VR\n xr.end();\n }\n else if (!isInXR && xr.isAvailable(pc.XRTYPE_VR)) {\n // Enter VR\n xrSessionType = 'vr';\n camera.camera!.startXr(pc.XRTYPE_VR, pc.XRSPACE_LOCALFLOOR, {\n callback: (err: Error | null) => {\n if (err) {\n console.error('[StorySplat Viewer] Failed to start VR:', err);\n xrSessionType = null;\n }\n }\n });\n }\n });\n }\n // AR button click handler\n if (uiElements.arButton) {\n uiElements.arButton.addEventListener('click', () => {\n if (isInXR && xrSessionType === 'ar') {\n // Exit AR\n xr.end();\n }\n else if (!isInXR && xr.isAvailable(pc.XRTYPE_AR)) {\n // Enter AR\n xrSessionType = 'ar';\n camera.camera!.startXr(pc.XRTYPE_AR, pc.XRSPACE_LOCALFLOOR, {\n callback: (err: Error | null) => {\n if (err) {\n console.error('[StorySplat Viewer] Failed to start AR:', err);\n xrSessionType = null;\n }\n }\n });\n }\n });\n }\n }\n // AR Content Display - 3D plane for showing hotspot info in XR mode\n let arContentEntity: pc.Entity | null = null;\n let arContentTexture: pc.Texture | null = null;\n let arContentVisible = false;\n // Helper for word wrapping text on canvas\n function wrapText(ctx: CanvasRenderingContext2D, text: string, x: number, y: number, maxWidth: number, lineHeight: number): number {\n const words = text.split(' ');\n let line = '';\n let currentY = y;\n for (const word of words) {\n const testLine = line + word + ' ';\n if (ctx.measureText(testLine).width > maxWidth && line) {\n ctx.fillText(line.trim(), x, currentY);\n line = word + ' ';\n currentY += lineHeight;\n }\n else {\n line = testLine;\n }\n }\n ctx.fillText(line.trim(), x, currentY);\n return currentY + lineHeight;\n }\n function showARContent(hotspot: HotspotData): void {\n if (!isInXR)\n return;\n if (!arContentEntity) {\n // Create plane entity for AR content display\n arContentEntity = new pc.Entity('arContentPlane');\n arContentEntity.addComponent('render', { type: 'plane' });\n arContentEntity.setLocalScale(0.8, 1, 0.45); // 16:9 aspect ratio roughly\n app.root.addChild(arContentEntity);\n }\n // Create dynamic texture with hotspot info\n const canvasWidth = 512;\n const canvasHeight = 288;\n const canvas = document.createElement('canvas');\n canvas.width = canvasWidth;\n canvas.height = canvasHeight;\n const ctx = canvas.getContext('2d')!;\n // Render background with rounded corners effect\n const bgColor = hotspot.backgroundColor || 'rgba(20, 20, 20, 0.95)';\n ctx.fillStyle = bgColor;\n ctx.fillRect(0, 0, canvasWidth, canvasHeight);\n // Add subtle border\n ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';\n ctx.lineWidth = 2;\n ctx.strokeRect(1, 1, canvasWidth - 2, canvasHeight - 2);\n const textColor = hotspot.textColor || '#ffffff';\n let currentY = 35;\n // Render title\n if (hotspot.title) {\n ctx.fillStyle = textColor;\n ctx.font = 'bold 28px Arial, sans-serif';\n ctx.fillText(hotspot.title, 20, currentY);\n currentY += 40;\n }\n // Render information with word wrap\n if (hotspot.information) {\n ctx.fillStyle = textColor;\n ctx.font = '18px Arial, sans-serif';\n currentY = wrapText(ctx, hotspot.information, 20, currentY, canvasWidth - 40, 24);\n }\n // Render \"Tap to close\" hint at bottom\n ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';\n ctx.font = '14px Arial, sans-serif';\n ctx.textAlign = 'center';\n ctx.fillText('Tap to close', canvasWidth / 2, canvasHeight - 15);\n ctx.textAlign = 'left';\n // Create/update texture\n if (!arContentTexture) {\n arContentTexture = new pc.Texture(app.graphicsDevice, {\n width: canvasWidth,\n height: canvasHeight,\n format: pc.PIXELFORMAT_RGBA8,\n mipmaps: false,\n minFilter: pc.FILTER_LINEAR,\n magFilter: pc.FILTER_LINEAR\n });\n }\n arContentTexture.setSource(canvas);\n // Create/update material\n const material = new pc.StandardMaterial();\n material.diffuse = new pc.Color(0, 0, 0);\n material.diffuseMap = arContentTexture;\n material.emissive = new pc.Color(1, 1, 1);\n material.emissiveMap = arContentTexture;\n material.useLighting = false;\n material.blendType = pc.BLEND_NORMAL;\n material.cull = pc.CULLFACE_NONE;\n material.depthWrite = true;\n material.update();\n if (arContentEntity.render && arContentEntity.render.meshInstances[0]) {\n arContentEntity.render.meshInstances[0].material = material;\n }\n arContentEntity.enabled = true;\n arContentVisible = true;\n console.log('[StorySplat Viewer] AR content displayed for hotspot:', hotspot.title);\n }\n function hideARContent(): void {\n if (arContentEntity) {\n arContentEntity.enabled = false;\n arContentVisible = false;\n }\n }\n function updateARContentPosition(): void {\n if (arContentEntity?.enabled && camera) {\n // Position the AR content panel 1.5 meters in front of camera, slightly below eye level\n const forward = camera.forward.clone();\n const cameraPos = camera.getPosition();\n const panelPos = cameraPos.clone().add(forward.mulScalar(1.5));\n panelPos.y -= 0.1; // Slightly below eye level for comfortable viewing\n arContentEntity.setPosition(panelPos);\n // Make the plane face the camera\n arContentEntity.lookAt(cameraPos);\n // Rotate 90 degrees on X axis since plane faces up by default\n arContentEntity.rotateLocal(90, 0, 0);\n }\n }\n // FPS counter update throttle\n let fpsUpdateTimer = 0;\n // Add update loop for CameraControls, CharacterController, and proximity checks\n app.on('update', (dt: number) => {\n // Update FPS counter (throttled to every 500ms)\n if (uiElements.fpsCounter) {\n fpsUpdateTimer += dt;\n if (fpsUpdateTimer >= 0.5) {\n fpsUpdateTimer = 0;\n updateFpsCounter(uiElements, 1 / dt);\n }\n }\n // Update either CameraControls or CharacterController based on mode\n if (currentCameraMode === 'walk' && characterController) {\n characterController.update(dt);\n }\n else {\n cameraControls.update(dt);\n }\n // Check proximity triggers for video hotspots (needed for explore/walk mode)\n if (!waypointControlEnabled) {\n updateProximityTriggers();\n }\n // Update waypoint audio based on proximity (for spatial audio)\n updateWaypointAudioProximity();\n // Update audio emitter proximity (for spatial audio)\n updateAudioEmitterProximity();\n // Check waypoint trigger distances for interaction execution\n checkWaypointTriggerDistance();\n // Update AR content panel position in XR mode\n if (isInXR && arContentVisible) {\n updateARContentPosition();\n }\n });\n // Listen to CameraControls joystick events for UI updates\n app.on('joystick:left', (bx: number, by: number, sx: number, sy: number) => {\n if (bx < 0 || by < 0) {\n // Joystick released\n updateJoystickPosition(uiElements, false, 0, 0, 60);\n }\n else {\n // Joystick active - calculate offset from base to stick\n const dx = sx - bx;\n const dy = sy - by;\n updateJoystickPosition(uiElements, true, dx, dy, 60);\n }\n });\n // Listen to right joystick (look zone) events for UI updates\n app.on('joystick:right', (bx: number, by: number, sx: number, sy: number) => {\n if (bx < 0 || by < 0) {\n // Look control released\n updateLookZoneState(uiElements, false);\n }\n else {\n // Look control active\n updateLookZoneState(uiElements, true);\n }\n });\n // Orbit/Fly text buttons in scroll controls (both desktop and mobile)\n const exploreBtns = uiElements.scrollControls?.querySelectorAll('.storysplat-explore-btn');\n const updateExploreBtnState = (activeMode: 'orbit' | 'fly') => {\n exploreBtns?.forEach(btn => {\n const mode = btn.getAttribute('data-explore-mode');\n btn.classList.toggle('selected', mode === activeMode);\n });\n };\n exploreBtns?.forEach(btn => {\n btn.addEventListener('click', () => {\n if (currentCameraMode !== 'explore')\n return;\n const mode = btn.getAttribute('data-explore-mode') as 'orbit' | 'fly';\n if (mode === 'orbit') {\n cameraControls.setMode('orbit');\n updateExploreBtnState('orbit');\n if (isMobile)\n setJoystickVisible(uiElements, false);\n }\n else {\n cameraControls.setMode('fly');\n updateExploreBtnState('fly');\n if (isMobile)\n setJoystickVisible(uiElements, true);\n }\n });\n });\n // Listen for CameraControls internal mode changes (e.g., after focus completes)\n app.on('cameracontrols:modechange', (mode: string) => {\n if (mode === 'orbit' || mode === 'fly') {\n updateExploreBtnState(mode as 'orbit' | 'fly');\n // Update joystick visibility on mobile\n if (isMobile && currentCameraMode === 'explore') {\n setJoystickVisible(uiElements, mode === 'fly');\n }\n }\n });\n // Proximity check function (called every frame in explore mode)\n function updateProximityTriggers(): void {\n const cameraPos = camera.getPosition();\n hotspotEntities.forEach((entity) => {\n const hotspot = entity.hotspotData;\n if (!hotspot)\n return;\n const triggerMode = entity.mediaTriggerMode || 'click';\n if (triggerMode !== 'proximity')\n return;\n const hotspotPos = entity.getPosition();\n const distance = cameraPos.distance(hotspotPos);\n const proximityDistance = entity.proximityDistance || 5;\n const isInProximity = distance <= proximityDistance;\n const wasInProximity = entity.wasInProximity || false;\n if (isInProximity && !wasInProximity) {\n // Entering proximity\n if (hotspot.type === 'video' && entity.videoElement) {\n playVideoHotspot(entity, hotspot);\n }\n else if (hotspot.type === 'gif') {\n entity.enabled = true;\n }\n else if ((hotspot.type === 'sphere' || hotspot.type === 'image') && entity.audioElements) {\n // Play audio for sphere/image hotspots with audio\n const audioElements = entity.audioElements as HotspotAudioElements;\n const audio = audioElements.audio;\n if (audio && audio.paused) {\n // Resume AudioContext on proximity trigger (may need user interaction first)\n if (audioElements.audioCtx && audioElements.audioCtx.state === 'suspended') {\n audioElements.audioCtx.resume();\n }\n audio.play().catch(err => console.error('[Audio] Hotspot audio play failed (proximity):', err));\n console.log('[Audio] Hotspot audio started (proximity):', hotspot.title);\n }\n }\n entity.wasInProximity = true;\n }\n else if (!isInProximity && wasInProximity) {\n // Leaving proximity\n if (hotspot.type === 'video' && entity.videoElement) {\n pauseVideoHotspot(entity);\n }\n else if (hotspot.type === 'gif') {\n entity.enabled = false;\n }\n else if ((hotspot.type === 'sphere' || hotspot.type === 'image') && entity.audioElements) {\n // Stop audio for sphere/image hotspots with audio\n const audioElements = entity.audioElements as HotspotAudioElements;\n const audio = audioElements.audio;\n if (audio && !audio.paused) {\n audio.pause();\n console.log('[Audio] Hotspot audio stopped (proximity):', hotspot.title);\n }\n }\n entity.wasInProximity = false;\n }\n });\n }\n // Mode switching function (Tour, Explore, or Walk)\n function setCameraMode(mode: string): void {\n // Disable previous mode's controllers\n if (currentCameraMode === 'walk' && characterController) {\n characterController.disable();\n }\n else if (currentCameraMode === 'explore') {\n cameraControls.disable();\n }\n currentCameraMode = mode;\n console.log('[StorySplat Viewer] Switching to mode:', mode);\n const isMobile = isMobileDevice();\n if (mode === 'walk' && characterController) {\n // Walk mode: Enable CharacterController (first-person with collisions)\n waypointControlEnabled = false;\n // Disable CameraControls\n cameraControls.disable();\n // Enable CharacterController\n characterController.enable();\n // Hide joystick (CharacterController uses keyboard/mouse)\n setJoystickVisible(uiElements, false);\n }\n else if (mode === 'explore') {\n // Explore mode: Enable CameraControls (orbit + fly + pan)\n // Disable CharacterController if active\n if (characterController) {\n characterController.disable();\n }\n // CRITICAL: Reset user rotation offsets and disable waypoint control\n userYawOffset = 0;\n userPitchOffset = 0;\n isUserDragging = false;\n waypointControlEnabled = false;\n // IMPORTANT: Call disable() first to clear any accumulated input from tour mode dragging\n // The CameraControls input sources accumulate mouse/touch input even when disabled,\n // and this would cause an offset when we enable and the update loop reads that input\n cameraControls.disable(); // Clears input buffers\n cameraControls.enable();\n cameraControls.enableOrbit = true;\n cameraControls.enableFly = true;\n cameraControls.enablePan = true;\n // Determine the explore sub-mode to apply\n const exploreDefault = initialSplatExploreMode || 'fly';\n // Start explore mode from the current camera position (both orbit and fly)\n if (targetCameraPosition && targetCameraRotation) {\n // Use syncFromPose to directly set the camera from the waypoint target values\n // This bypasses the camera entity's current state which may have user rotation applied\n cameraControls.syncFromPose(targetCameraPosition, targetCameraRotation);\n console.log('[StorySplat Viewer] Synced camera from waypoint pose for explore mode');\n }\n else {\n cameraControls.syncFromCamera();\n }\n // Try to find a better focus point using the picker (async improvement)\n const findBetterFocusPoint = async () => {\n try {\n const pickerScale = 0.25;\n picker.resize(Math.floor(canvasEl.clientWidth * pickerScale), Math.floor(canvasEl.clientHeight * pickerScale));\n const worldLayer = app.scene.layers.getLayerByName('World');\n if (worldLayer) {\n picker.prepare(camera.camera!, app.scene, [worldLayer]);\n const centerX = Math.floor(canvasEl.clientWidth * 0.5 * pickerScale);\n const centerY = Math.floor(canvasEl.clientHeight * 0.5 * pickerScale);\n const worldPoint = await picker.getWorldPointAsync(centerX, centerY);\n if (worldPoint) {\n const cameraPos = camera.getPosition();\n const distance = cameraPos.distance(worldPoint);\n if (distance > 0.5 && distance < 500) {\n cameraControls.syncFromCamera(worldPoint);\n console.log('[StorySplat Viewer] Updated focus target at distance:', distance.toFixed(2));\n }\n }\n }\n }\n catch (err) {\n // Picking failed, keep default\n }\n };\n findBetterFocusPoint();\n // Apply the explore sub-mode (already determined above)\n cameraControls.setMode(exploreDefault);\n updateExploreBtnState(exploreDefault as 'orbit' | 'fly');\n if (isMobile) {\n setJoystickVisible(uiElements, exploreDefault === 'fly');\n }\n }\n else {\n // Tour mode - disable camera controls, enable waypoint control\n cameraControls.disable();\n if (characterController) {\n characterController.disable();\n }\n waypointControlEnabled = true;\n // Hide joystick in tour mode\n setJoystickVisible(uiElements, false);\n }\n events.emit('modeChange', { mode });\n }\n // Update preloader progress helper (defined early so loadSplat can use it)\n const updateProgress = (progress: number, text?: string) => {\n if (uiElements.preloader) {\n updatePreloaderProgress(uiElements.preloader, progress, text, getButtonLabel(currentButtonLabels, 'loading'));\n }\n events.emit('progress', { progress, text });\n };\n // Helper to check if URL is hosted on StorySplat servers (Firebase Storage)\n // Only count bandwidth for files we host, not self-hosted files\n function isStorySplatHostedUrl(url: string): boolean {\n const storySplatHosts = [\n 'firebasestorage.googleapis.com/v0/b/story-splat',\n 'storage.googleapis.com/story-splat',\n 'storysplat.com',\n 'discover.storysplat.com'\n ];\n return storySplatHosts.some(host => url.includes(host));\n }\n // Load splat\n async function loadSplat(): Promise<void> {\n // Track bandwidth used during loading (only for StorySplat-hosted files)\n let bandwidthUsed = 0;\n let loadedUrl = ''; // Track which URL was successfully loaded\n // Build URL list with priority: LOD streaming > SOG > PLY/Other formats\n // This ensures backward compatibility with all formats while preferring optimal ones\n const urls: string[] = [];\n console.log('[SPLAT] Building URL priority list...');\n console.log('[SPLAT] Available URLs:', {\n lodMetaUrl: config.lodMetaUrl || '(none)',\n sogUrl: config.sogUrl || '(none)',\n splatUrl: config.splatUrl || '(none)',\n fallbackUrls: config.fallbackUrls?.length || 0\n });\n // Highest priority: LOD streaming format (lod-meta.json) if available\n // NOTE: lod-meta.json should have RELATIVE paths for chunk meta.json files (e.g., \"0_0/meta.json\")\n // PlayCanvas resolves these relative to the lod-meta.json base URL\n // Only the chunk meta.json files should have full URLs for their texture references\n if (config.lodMetaUrl) {\n urls.push(config.lodMetaUrl);\n console.log('[SPLAT] ✓ LOD streaming URL added (highest priority):', config.lodMetaUrl);\n }\n // Second priority: SOG compressed format\n if (config.sogUrl) {\n urls.push(config.sogUrl);\n console.log('[SPLAT] ✓ SOG URL added (second priority):', config.sogUrl);\n }\n // Third priority: Original format (PLY, SPLAT, etc.)\n if (config.splatUrl) {\n urls.push(config.splatUrl);\n console.log('[SPLAT] ✓ Original splat URL added (third priority):', config.splatUrl);\n }\n // Fallback URLs for additional format support\n if (config.fallbackUrls) {\n urls.push(...config.fallbackUrls);\n console.log('[SPLAT] ✓ Fallback URLs added:', config.fallbackUrls);\n }\n console.log('[SPLAT] Final URL priority order:', urls);\n console.log('[SPLAT] Will try URLs in order: LOD streaming > SOG > PLY/Other');\n updateProgress(0.3, getButtonLabel(currentButtonLabels, 'loading'));\n for (const url of urls) {\n if (!url)\n continue;\n try {\n // Determine asset type based on extension\n const ext = url.split('.').pop()?.toLowerCase() || 'splat';\n const assetType = 'gsplat'; // PlayCanvas uses 'gsplat' for all gaussian splat formats\n // Check format type for logging\n const isLodFormat = url.includes('lod-meta.json');\n const urlIsSogFormat = url.includes('.sog') || isLodFormat;\n console.log('[SPLAT] Attempting to load URL:', url);\n console.log('[SPLAT] Format detection:', {\n extension: ext,\n isLodStreaming: isLodFormat,\n isSogFormat: urlIsSogFormat,\n assetType: assetType\n });\n const asset = new pc.Asset('splat-' + Date.now(), assetType, { url });\n // Note: lod-meta.json filenames are rewritten to absolute CDN URLs during upload,\n // so PlayCanvas's GSplatOctree uses them as-is (path.isRelativePath returns false).\n // No mapUrl workaround is needed.\n // Track asset loading progress\n asset.on('progress', (received: number, total: number) => {\n if (total > 0) {\n // Track bandwidth: use received bytes (actual data transferred)\n // Only track if this is a StorySplat-hosted URL\n if (isStorySplatHostedUrl(url)) {\n bandwidthUsed = received;\n }\n // Map progress from 0.3 to 0.9 (leaving 0.9-1.0 for post-load setup)\n const loadProgress = 0.3 + (received / total) * 0.6;\n const percent = Math.round((received / total) * 100);\n if (percent % 25 === 0 || percent === 100) { // Log at 25%, 50%, 75%, 100%\n console.log(`[SPLAT] Loading progress: ${percent}% (${(received / 1024 / 1024).toFixed(2)}MB / ${(total / 1024 / 1024).toFixed(2)}MB)`);\n }\n updateProgress(loadProgress, `${getButtonLabel(currentButtonLabels, 'loading')} ${percent}%`);\n }\n });\n // Track which URL we're attempting to load\n loadedUrl = url;\n await new Promise<void>((resolve, reject) => {\n let hasRejected = false;\n // Catch unhandled promise rejections during loading (e.g., SOG v1 parse errors)\n // Listen for SOG formats which may have deprecated format issues\n const unhandledRejectionHandler = urlIsSogFormat ? (event: PromiseRejectionEvent) => {\n if (hasRejected)\n return;\n const errorMsg = event.reason?.message || String(event.reason);\n // Check if this is a SOG parsing error (deprecated v1 format)\n if (errorMsg.includes('shape') || errorMsg.includes('upgradeMeta')) {\n console.warn('[SPLAT] ⚠️ Parse error detected (likely deprecated v1 format):', errorMsg);\n hasRejected = true;\n event.preventDefault(); // Prevent console error\n // Emit a deprecation warning event for UI to display\n events.emit('warning', {\n type: 'deprecated_format',\n message: 'This scene uses an outdated SOG format. Please re-upload your scene with the latest tools for better performance.',\n details: 'SOG v1 format is deprecated. Use splat-transform v2+ to convert your PLY files.',\n url: url\n });\n console.log('[SPLAT] Will try fallback URL if available...');\n reject(new Error(`SOG parse error: ${errorMsg}`));\n }\n } : null;\n if (urlIsSogFormat && unhandledRejectionHandler) {\n console.log('[SPLAT] Registered error handler for SOG format parsing');\n window.addEventListener('unhandledrejection', unhandledRejectionHandler);\n }\n // Cleanup handler when done\n const cleanup = () => {\n if (urlIsSogFormat && unhandledRejectionHandler) {\n window.removeEventListener('unhandledrejection', unhandledRejectionHandler);\n }\n };\n // Use asset.ready() like HTML export - this waits for asset AND resources to be ready\n asset.ready(() => {\n // Check if viewer was destroyed while loading\n if (isDestroyed) {\n console.log('[SPLAT] Ignoring load - viewer was destroyed during loading');\n cleanup();\n reject(new Error('Viewer destroyed'));\n return;\n }\n console.log('[SPLAT] ✓ Asset loaded and resources ready');\n try {\n splatEntity = new pc.Entity('splat');\n splatEntity.addComponent('gsplat', {\n asset: asset,\n unified: true // Enable unified sorting with other transparent objects (hotspots)\n });\n // Set LOD distances for LOD streaming format\n // This enables distance-based quality switching when using lod-meta.json\n const gs = splatEntity.gsplat as GSplatComponentWithRuntime | undefined;\n if (gs && isLodStreamingFormat(url)) {\n gs.lodDistances = [...lodPreset.lodDistances];\n console.log('[SPLAT] ✓ LOD streaming enabled for this splat');\n console.log('[SPLAT] LOD distances configured:', {\n distances: lodPreset.lodDistances,\n description: 'Quality levels switch at these camera distances',\n preset: lodPresetName\n });\n }\n else if (urlIsSogFormat) {\n console.log('[SPLAT] ✓ Single SOG file loaded (no LOD streaming)');\n }\n else {\n console.log('[SPLAT] Standard format loaded (PLY/SPLAT)');\n }\n // Apply scale - match BabylonJS behavior exactly\n // In BabylonJS: invertYScale negates Y, and when invertX !== invertY, Z is also negated to maintain handedness\n const scale = config.scale || { x: 1, y: 1, z: 1 };\n const invertX = config.invertXScale || false;\n const invertY = config.invertYScale || false;\n const finalScale = {\n x: invertX ? -scale.x : scale.x,\n y: invertY ? -scale.y : scale.y,\n z: (invertX !== invertY) ? -scale.z : scale.z // Invert Z when only one of X or Y is inverted\n };\n splatEntity.setLocalScale(finalScale.x, finalScale.y, finalScale.z);\n // Apply position (negate Z for Babylon->PlayCanvas coordinate conversion)\n const pos = config.position || [0, 0, 0];\n splatEntity.setPosition(pos[0], pos[1], -pos[2]);\n // Apply rotation - convert from BabylonJS (left-handed) to PlayCanvas (right-handed)\n // ALWAYS apply 180° X base correction for splat orientation, then add user rotation\n const rot = config.rotation || [0, 0, 0];\n const baseRotX = 180; // Base correction to flip splat for PlayCanvas coordinate system\n const finalRotDeg: number[] = [\n baseRotX + rot[0] * (180 / Math.PI),\n rot[1] * (180 / Math.PI),\n -rot[2] * (180 / Math.PI)\n ];\n splatEntity.setEulerAngles(finalRotDeg[0], finalRotDeg[1], finalRotDeg[2]);\n console.log('[StorySplat Viewer] Splat transform applied:', {\n scale: finalScale,\n position: [pos[0], pos[1], -pos[2]],\n rotation: finalRotDeg,\n userRotation: rot,\n invertXScale: invertX,\n invertYScale: invertY\n });\n app.root.addChild(splatEntity);\n console.log('[StorySplat Viewer] Splat entity added to scene');\n // Set alphaClip for GSplat picking support (required for pc.Picker to work with splats)\n // Keep this LOW to reduce \"holes\" in the splat depth buffer, which can cause\n // hotspots/portals behind geometry to show through.\n if (splatEntity.gsplat?.material) {\n splatEntity.gsplat.material.setParameter('alphaClip', 0.01);\n console.log('[StorySplat Viewer] GSplat alphaClip set for picking support');\n }\n // Apply reveal effect if configured (but start disabled - will enable after preloader hides)\n const revealPresetName = (options.revealEffect || scene.revealEffect || 'medium') as RevealPreset;\n const revealConfig = getRevealPreset(revealPresetName);\n if (revealConfig) {\n // Add script component for reveal effect\n splatEntity.addComponent('script');\n // Create the reveal effect (use getter to get class after app is initialized)\n const GsplatRevealRadialClass = getGsplatRevealRadialClass();\n revealScript = ((splatEntity.script as ScriptComponentWithCreate | undefined)?.create?.(GsplatRevealRadialClass) as RevealEffectScript | undefined) ?? null;\n if (revealScript) {\n // Start disabled - will be enabled after preloader hides\n revealScript.enabled = false;\n // Apply preset configuration\n revealScript.center.set(0, 0, 0);\n revealScript.speed = revealConfig.speed;\n revealScript.acceleration = revealConfig.acceleration;\n revealScript.delay = revealConfig.delay;\n revealScript.oscillationIntensity = revealConfig.oscillationIntensity;\n revealScript.dotTint.set(revealConfig.dotTint.r, revealConfig.dotTint.g, revealConfig.dotTint.b);\n revealScript.waveTint.set(revealConfig.waveTint.r, revealConfig.waveTint.g, revealConfig.waveTint.b);\n revealScript.endRadius = revealConfig.endRadius;\n console.log('[StorySplat Viewer] Reveal effect configured (waiting to start):', revealPresetName);\n }\n }\n else {\n console.log('[StorySplat Viewer] Reveal effect disabled');\n }\n // Wait a brief moment to let async texture loading errors surface\n // before resolving (SOG v1 parse errors happen asynchronously)\n setTimeout(() => {\n if (hasRejected)\n return; // Already rejected by unhandled rejection handler\n cleanup();\n resolve();\n }, 100);\n }\n catch (syncError) {\n console.error('[StorySplat Viewer] Error during gsplat setup:', syncError);\n cleanup();\n reject(syncError);\n }\n });\n asset.on('error', (err: unknown) => {\n // Enhanced error logging to help diagnose SOG/PLY loading issues\n const errObj = err as Record<string, unknown> | null;\n console.error('[SPLAT] ✗ Asset load error:', {\n url,\n assetType,\n isSogFormat: urlIsSogFormat,\n isLodFormat: isLodFormat,\n error: err,\n message: errObj?.message || 'Unknown error',\n status: errObj?.status || errObj?.statusCode || 'N/A'\n });\n cleanup();\n reject(err);\n });\n app.assets.add(asset);\n // For LOD streaming URLs, bypass PlayCanvas's parser routing.\n // PlayCanvas selects the parser via path.getBasename(url) === 'lod-meta.json',\n // but Firebase/CDN URLs use %2F encoding which makes getBasename() return the\n // entire encoded path instead of just 'lod-meta.json'. This causes PlayCanvas\n // to route to SogParser instead of GSplatOctreeParser, which crashes.\n // Fix: directly invoke the octree parser for LOD URLs.\n if (isLodFormat) {\n const handler = app.loader.getHandler('gsplat') as GsplatHandlerWithParsers | null;\n const octreeParser = handler?.parsers?.octree;\n if (octreeParser) {\n console.log('[SPLAT] Using direct octree parser for LOD URL (bypassing basename routing)');\n const urlObj = { load: url, original: url };\n (octreeParser as { load: (urlObj: { load: string; original?: string }, callback: (err: unknown, resource: unknown) => void, asset?: pc.Asset) => void }).load(urlObj, (err: unknown, resource: unknown) => {\n if (err) {\n console.error('[SPLAT] Octree parser error:', err);\n asset.fire('error', err);\n return;\n }\n // Wire up the resource the same way PlayCanvas's asset registry does\n asset.resource = resource as object;\n asset.loaded = true;\n asset.fire('load', asset);\n }, asset);\n }\n else {\n console.warn('[SPLAT] Could not access octree parser, falling back to standard load');\n app.assets.load(asset);\n }\n }\n else {\n app.assets.load(asset);\n }\n });\n const isHostedOnStorySplat = isStorySplatHostedUrl(loadedUrl);\n // Only report bandwidth for StorySplat-hosted files\n events.emit('loaded', {\n bandwidthUsed: isHostedOnStorySplat ? bandwidthUsed : 0,\n isStorySplatHosted: isHostedOnStorySplat\n });\n console.log('[SPLAT] ✓✓✓ SPLAT LOADED SUCCESSFULLY ✓✓✓');\n console.log('[SPLAT] Load summary:', {\n url: loadedUrl,\n format: isLodFormat ? 'LOD streaming (lod-meta.json)' : urlIsSogFormat ? 'SOG (compressed)' : 'Standard (PLY/SPLAT)',\n lodEnabled: isLodFormat,\n lodPreset: isLodFormat ? lodPresetName : 'N/A',\n bandwidthUsed: `${(bandwidthUsed / 1024 / 1024).toFixed(2)}MB`,\n isStorySplatHosted: isHostedOnStorySplat,\n bandwidthCounted: isHostedOnStorySplat ? 'Yes' : 'No (self-hosted)'\n });\n return;\n }\n catch (err) {\n console.warn('[SPLAT] ✗ Failed to load URL, trying next fallback:', url);\n console.warn('[SPLAT] Error:', err);\n }\n }\n console.error('[SPLAT] ✗✗✗ FAILED TO LOAD SPLAT FROM ANY URL ✗✗✗');\n console.error('[SPLAT] Tried URLs:', urls);\n events.emit('error', new Error('Failed to load splat from any URL'));\n }\n // Load 4DGS frame sequence\n async function loadFrameSequence(): Promise<void> {\n if (!scene.frameSequence || !scene.frameSequence.frameUrls || scene.frameSequence.frameUrls.length === 0) {\n throw new Error('No frame sequence URLs provided');\n }\n console.log('[StorySplat Viewer] Loading 4DGS frame sequence:', scene.frameSequence.frameUrls.length, 'frames');\n updateProgress(0.3, 'Loading 4DGS frames...');\n frameSequencePlayer = new FrameSequencePlayer(app, {\n frameUrls: scene.frameSequence.frameUrls,\n fps: scene.frameSequence.fps || 24,\n loop: scene.frameSequence.loop !== false,\n preloadCount: scene.frameSequence.preloadCount || 5,\n autoplay: scene.frameSequence.autoplay || false,\n }, {\n onFrameChange: (frame, total) => {\n events.emit('frameChange', frame, total);\n },\n onLoadProgress: (loaded, total) => {\n const progress = 0.3 + (loaded / total) * 0.6;\n updateProgress(progress, `Loading frames... ${loaded}/${total}`);\n },\n onError: (error) => {\n console.error('[StorySplat Viewer] Frame sequence error:', error);\n events.emit('error', new Error(error));\n }\n });\n // Forward 'complete' event as 'frameComplete' for the public API\n frameSequencePlayer.on('complete', () => {\n events.emit('frameComplete');\n });\n // Hook into app update loop\n app.on('update', (dt: number) => {\n if (frameSequencePlayer && !isDestroyed) {\n frameSequencePlayer.update(dt);\n }\n });\n console.log('[StorySplat Viewer] 4DGS frame sequence player initialized');\n // Frame sequences don't track bandwidth yet - emit 0 for now\n events.emit('loaded', { bandwidthUsed: 0, isStorySplatHosted: false });\n }\n // Helper to convert waypoint rotation to PlayCanvas quaternion\n // Matching POC waypointNavigation.ts exactly - NO yFlip\n function convertWaypointRotation(rot: LegacyQuat): pc.Quat {\n if ('_w' in rot || 'w' in rot) {\n // Quaternion format - BabylonJS to PlayCanvas conversion\n // Negate X and Y for handedness conversion (matching POC exactly)\n const qx = rot._x ?? rot.x ?? 0;\n const qy = rot._y ?? rot.y ?? 0;\n const qz = rot._z ?? rot.z ?? 0;\n const qw = rot._w ?? rot.w ?? 1;\n return new pc.Quat(-qx, -qy, qz, qw);\n }\n else if ('x' in rot && 'y' in rot && 'z' in rot) {\n // Euler angles\n const result = new pc.Quat();\n result.setFromEulerAngles(rot.x || 0, rot.y || 0, rot.z || 0);\n return result;\n }\n else {\n return new pc.Quat();\n }\n }\n // =========================================================\n // SPLATSWAP SYSTEM\n // Handles swapping between multiple splat files during navigation\n // =========================================================\n /**\n * Hide a preloaded splat (Option 1: keep in memory)\n */\n function hideSplat(entity: pc.Entity): void {\n entity.enabled = false;\n console.log('[SplatSwap] Splat hidden');\n }\n /**\n * Dispose of a preloaded splat (Option 2: free memory)\n */\n function disposeSplat(url: string): void {\n const entity = preloadedSplats.get(url);\n if (entity) {\n entity.destroy();\n preloadedSplats.delete(url);\n console.log('[SplatSwap] Splat disposed:', url);\n }\n }\n /**\n * Show a preloaded splat\n */\n function showSplat(url: string): boolean {\n const entity = preloadedSplats.get(url);\n if (!entity)\n return false;\n entity.enabled = true;\n currentSplatUrl = url;\n console.log('[SplatSwap] Splat shown:', url);\n return true;\n }\n /**\n * Preload a splat in the background without displaying it\n */\n async function preloadSplat(url: string): Promise<void> {\n if (preloadedSplats.has(url))\n return;\n if (url === currentSplatUrl)\n return;\n if (isDestroyed)\n return;\n console.log('[SplatSwap] Preloading splat:', url);\n try {\n const asset = new pc.Asset('splat-preload-' + Date.now(), 'gsplat', { url });\n await new Promise<void>((resolve, reject) => {\n asset.ready(() => {\n if (isDestroyed) {\n reject(new Error('Viewer destroyed'));\n return;\n }\n const entity = new pc.Entity('splat-preload');\n entity.addComponent('gsplat', { asset, unified: true });\n // Apply scale/position/rotation matching main splat\n const scale = config.scale || { x: 1, y: 1, z: 1 };\n const invertX = config.invertXScale || false;\n const invertY = config.invertYScale || false;\n const finalScale = {\n x: invertX ? -scale.x : scale.x,\n y: invertY ? -scale.y : scale.y,\n z: (invertX !== invertY) ? -scale.z : scale.z\n };\n entity.setLocalScale(finalScale.x, finalScale.y, finalScale.z);\n const pos = config.position || [0, 0, 0];\n entity.setPosition(pos[0], pos[1], -pos[2]);\n const rot = config.rotation || [0, 0, 0];\n const baseRotX = 180; // Base correction for PlayCanvas coordinate system\n const finalRotDeg = [\n baseRotX + rot[0] * (180 / Math.PI),\n rot[1] * (180 / Math.PI),\n -rot[2] * (180 / Math.PI)\n ];\n entity.setEulerAngles(finalRotDeg[0], finalRotDeg[1], finalRotDeg[2]);\n // Start hidden\n entity.enabled = false;\n app.root.addChild(entity);\n preloadedSplats.set(url, entity);\n console.log('[SplatSwap] Preload complete:', url);\n resolve();\n });\n asset.on('error', (err: unknown) => {\n console.error('[SplatSwap] Preload error:', err);\n reject(err);\n });\n app.assets.add(asset);\n app.assets.load(asset);\n });\n }\n catch (error) {\n console.error('[SplatSwap] Error preloading:', url, error);\n }\n }\n /**\n * Apply the appropriate explore sub-mode for a splat\n * Only applies if currently in explore mode\n */\n function applyExploreModeForSplat(splatExploreMode?: 'orbit' | 'fly', isOriginal: boolean = false): void {\n if (currentCameraMode !== 'explore')\n return;\n // Determine the target mode: use splat-specific mode, or initial mode for original\n const targetMode = isOriginal\n ? initialSplatExploreMode\n : (splatExploreMode || initialSplatExploreMode);\n if (targetMode && cameraControls) {\n const currentMode = cameraControls.mode;\n if (currentMode !== targetMode) {\n cameraControls.setMode(targetMode);\n console.log(`[SplatSwap] Switching explore sub-mode to: ${targetMode}`);\n // Update UI\n updateExploreBtnState(targetMode as 'orbit' | 'fly');\n if (isMobile) {\n setJoystickVisible(uiElements, targetMode === 'fly');\n }\n }\n }\n }\n /**\n * Preload the next splat in the sequence\n */\n async function preloadNextSplat(currentIndex: number): Promise<void> {\n if (!additionalSplats || additionalSplats.length === 0)\n return;\n const nextIndex = (currentIndex + 1) % additionalSplats.length;\n const nextSplat = additionalSplats[nextIndex];\n if (nextSplat && nextSplat.url) {\n await preloadSplat(nextSplat.url);\n }\n }\n /**\n * Load a splat (switch to it, handling show/hide or dispose)\n */\n async function loadSwapSplat(url: string): Promise<void> {\n if (url === currentSplatUrl)\n return;\n if (isLoadingSplat)\n return;\n if (isDestroyed)\n return;\n isLoadingSplat = true;\n console.log('[SplatSwap] Switching to splat:', url);\n try {\n // Hide/dispose current splat\n if (currentSplatUrl && preloadedSplats.has(currentSplatUrl)) {\n if (keepMeshesInMemory) {\n const currentEntity = preloadedSplats.get(currentSplatUrl);\n if (currentEntity)\n hideSplat(currentEntity);\n }\n else {\n disposeSplat(currentSplatUrl);\n }\n }\n else if (splatEntity) {\n // Hide the initial splat entity\n splatEntity.enabled = false;\n }\n // Show new splat if already preloaded\n if (preloadedSplats.has(url)) {\n showSplat(url);\n events.emit('splatChange', { url, isOriginal: false });\n }\n else {\n // Load new splat\n const asset = new pc.Asset('splat-swap-' + Date.now(), 'gsplat', { url });\n await new Promise<void>((resolve, reject) => {\n asset.ready(() => {\n if (isDestroyed) {\n reject(new Error('Viewer destroyed'));\n return;\n }\n const entity = new pc.Entity('splat-swap');\n entity.addComponent('gsplat', { asset, unified: true });\n // Apply scale/position/rotation\n const scale = config.scale || { x: 1, y: 1, z: 1 };\n const invertX = config.invertXScale || false;\n const invertY = config.invertYScale || false;\n const finalScale = {\n x: invertX ? -scale.x : scale.x,\n y: invertY ? -scale.y : scale.y,\n z: (invertX !== invertY) ? -scale.z : scale.z\n };\n entity.setLocalScale(finalScale.x, finalScale.y, finalScale.z);\n const pos = config.position || [0, 0, 0];\n entity.setPosition(pos[0], pos[1], -pos[2]);\n const rot = config.rotation || [0, 0, 0];\n const baseRotX = 180; // Base correction for PlayCanvas coordinate system\n const finalRotDeg = [\n baseRotX + rot[0] * (180 / Math.PI),\n rot[1] * (180 / Math.PI),\n -rot[2] * (180 / Math.PI)\n ];\n entity.setEulerAngles(finalRotDeg[0], finalRotDeg[1], finalRotDeg[2]);\n app.root.addChild(entity);\n preloadedSplats.set(url, entity);\n currentSplatUrl = url;\n events.emit('splatChange', { url, isOriginal: false });\n console.log('[SplatSwap] New splat loaded and shown:', url);\n resolve();\n });\n asset.on('error', (err: unknown) => {\n console.error('[SplatSwap] Load error:', err);\n reject(err);\n });\n app.assets.add(asset);\n app.assets.load(asset);\n });\n }\n // Background preload next splat\n const swapIndex = additionalSplats.findIndex(s => s.url === url);\n if (swapIndex !== -1) {\n preloadNextSplat(swapIndex);\n }\n }\n catch (error) {\n console.error('[SplatSwap] Error switching splat:', error);\n }\n finally {\n isLoadingSplat = false;\n initialSplatLoadDone = true;\n }\n }\n /**\n * Get the primary splat URL (first URL tried in loadSplat)\n */\n function getPrimarySplatUrl(): string {\n if (config.sogUrl)\n return config.sogUrl;\n if (config.splatUrl)\n return config.splatUrl;\n if (config.fallbackUrls && config.fallbackUrls.length > 0)\n return config.fallbackUrls[0];\n return '';\n }\n /**\n * Update splats based on current progress (called from navigation loop)\n */\n function updateSplats(): void {\n if (!additionalSplats || additionalSplats.length === 0)\n return;\n const numWaypoints = config.waypoints?.length || 1;\n const percentage = currentProgress * 100;\n const waypointIndex = Math.round(currentProgress * Math.max(1, numWaypoints - 1));\n // Skip if no meaningful change\n if (Math.abs(percentage - lastSplatCheckProgress) < 0.1 &&\n waypointIndex === lastSplatCheckWaypointIndex) {\n return;\n }\n lastSplatCheckProgress = percentage;\n lastSplatCheckWaypointIndex = waypointIndex;\n // Find the best matching splat based on progress\n // Use separate tracking for waypoint and percentage triggers to avoid\n // comparing different value ranges (waypoint 0-N vs percentage 0-100)\n type SplatSwapType = (typeof additionalSplats)[number];\n let bestWaypointSplat: SplatSwapType | null = null;\n let bestPercentageSplat: SplatSwapType | null = null;\n let bestWaypointTrigger = -Infinity;\n let bestPercentageTrigger = -Infinity;\n for (const splat of additionalSplats) {\n if (splat.waypointIndex !== -1) {\n // Trigger by waypoint index\n if (waypointIndex >= splat.waypointIndex && splat.waypointIndex > bestWaypointTrigger) {\n bestWaypointTrigger = splat.waypointIndex;\n bestWaypointSplat = splat;\n }\n }\n else if (splat.percentage !== -1) {\n // Trigger by percentage\n if (percentage >= splat.percentage && splat.percentage > bestPercentageTrigger) {\n bestPercentageTrigger = splat.percentage;\n bestPercentageSplat = splat;\n }\n }\n }\n // Prefer percentage-based triggers over waypoint-based (percentage is more precise)\n const bestSplat = bestPercentageSplat || bestWaypointSplat;\n // Check if best splat is a \"return to original\" marker\n const isReturnToOriginal = bestSplat && bestSplat.url === ORIGINAL_SPLAT_URL;\n // Determine target URL\n const primaryUrl = getPrimarySplatUrl();\n const targetUrl = bestSplat\n ? (isReturnToOriginal ? primaryUrl : bestSplat.url)\n : primaryUrl;\n // Switch splat if needed\n if (targetUrl && targetUrl !== currentSplatUrl) {\n if (targetUrl === primaryUrl && splatEntity && !currentSplatUrl) {\n // First load - just mark current URL\n currentSplatUrl = primaryUrl;\n }\n else if (targetUrl === primaryUrl && splatEntity) {\n // Return to primary splat\n // Hide swap splats and show primary\n preloadedSplats.forEach((entity, url) => {\n if (url !== primaryUrl) {\n if (keepMeshesInMemory) {\n hideSplat(entity);\n }\n else {\n disposeSplat(url);\n }\n }\n });\n if (splatEntity)\n splatEntity.enabled = true;\n currentSplatUrl = primaryUrl;\n events.emit('splatChange', { url: primaryUrl, isOriginal: true });\n console.log('[SplatSwap] Returned to primary splat');\n // Apply the initial splat's explore mode\n applyExploreModeForSplat(undefined, true);\n }\n else {\n // Switch to a different splat\n loadSwapSplat(targetUrl);\n // Apply the splat's explore mode if specified\n if (bestSplat) {\n applyExploreModeForSplat(bestSplat.defaultExploreMode, false);\n }\n }\n // Apply per-splat skybox if specified\n if (bestSplat && bestSplat.skyboxUrl && !isReturnToOriginal) {\n applySkybox(bestSplat.skyboxUrl, bestSplat.skyboxRotation || 0);\n }\n }\n }\n /**\n * Manually switch back to the original/initial splat\n */\n function goToOriginalSplat(): void {\n const primaryUrl = getPrimarySplatUrl();\n if (currentSplatUrl === primaryUrl)\n return;\n // Hide all swap splats\n preloadedSplats.forEach((entity, url) => {\n if (url !== primaryUrl) {\n if (keepMeshesInMemory) {\n hideSplat(entity);\n }\n else {\n disposeSplat(url);\n }\n }\n });\n // Show original splat\n if (splatEntity)\n splatEntity.enabled = true;\n currentSplatUrl = primaryUrl;\n events.emit('splatChange', { url: primaryUrl, isOriginal: true });\n console.log('[SplatSwap] Manually returned to original splat');\n // Apply the initial splat's explore mode\n applyExploreModeForSplat(undefined, true);\n }\n /**\n * Get the URL of the currently displayed splat\n */\n function getCurrentSplatUrl(): string {\n return currentSplatUrl || getPrimarySplatUrl();\n }\n /**\n * Check if currently showing the original splat\n */\n function isShowingOriginalSplat(): boolean {\n return currentSplatUrl === getPrimarySplatUrl() || currentSplatUrl === null;\n }\n /**\n * Apply a skybox (for per-splat skyboxes)\n * Note: For proper cubemap skyboxes, use 6-face URLs or HDR/EXR environment maps\n * This simplified version loads a single texture and creates a basic skybox\n */\n function applySkybox(url: string, rotation: number = 0): void {\n console.log('[SplatSwap] Applying skybox:', url, 'rotation:', rotation);\n // Load skybox as a cubemap texture\n const skyboxAsset = new pc.Asset('skybox-swap-' + Date.now(), 'cubemap', {\n url: url\n }, {\n // Try to load as RGBM for HDR support\n type: pc.TEXTURETYPE_RGBM,\n mipmaps: true\n });\n skyboxAsset.ready(() => {\n if (isDestroyed)\n return;\n try {\n // Set as scene skybox - cast to Texture to satisfy TypeScript\n app.scene.skybox = skyboxAsset.resource as pc.Texture;\n app.scene.skyboxMip = 0; // Use highest quality mip\n // Apply rotation via quaternion (convert radians to Euler then to Quat)\n if (rotation !== 0) {\n const rotQuat = new pc.Quat();\n rotQuat.setFromEulerAngles(0, rotation * (180 / Math.PI), 0);\n app.scene.skyboxRotation = rotQuat;\n }\n console.log('[SplatSwap] Skybox applied successfully');\n }\n catch (error) {\n console.error('[SplatSwap] Error applying skybox:', error);\n }\n });\n skyboxAsset.on('error', (err: unknown) => {\n console.error('[SplatSwap] Skybox load error:', err);\n });\n app.assets.add(skyboxAsset);\n app.assets.load(skyboxAsset);\n }\n // =========================================================\n // END SPLATSWAP SYSTEM\n // =========================================================\n // Progress-based navigation (matching HTML export)\n let currentProgress = 0; // 0 to 1\n let targetProgress = 0; // Scroll target for inertia (currentProgress lerps toward this)\n let isAnimatingProgress = false; // Flag to skip inertia during button animations\n const totalDuration = config.waypoints?.reduce((sum, wp) => sum + (wp.duration || 2000), 0) || 1;\n // Convert autoPlaySpeed from HTML format (per-frame path units) to progress per second\n // HTML: scrollTarget += autoPlaySpeed * animationRatio (per frame, path units 0 to pathLength-1)\n // NPM: progress += playbackSpeed * deltaTime (per frame, progress 0 to 1)\n // pathLength = (numWaypoints - 1) * 20 subdivisions\n // At 60fps: autoPlaySpeed * 60 path units per second\n // Progress per second = (autoPlaySpeed * 60) / pathLength\n const numWaypoints = config.waypoints?.length || 1;\n const pathLength = Math.max(1, (numWaypoints - 1) * 20);\n const playbackSpeed = config.autoplaySpeed !== undefined\n ? (config.autoplaySpeed * 60) / pathLength\n : 1000 / totalDuration;\n // Normalize loopMode: handle boolean values (true='loop', false='none') and string values\n // Scene data may have boolean loopMode from older versions\n const rawLoopMode = config.loopMode as unknown;\n let loopMode: 'loop' | 'pingpong' | 'none';\n if (rawLoopMode === true) {\n loopMode = 'loop';\n }\n else if (rawLoopMode === false) {\n loopMode = 'none';\n }\n else if (rawLoopMode === 'loop' || rawLoopMode === 'pingpong' || rawLoopMode === 'none') {\n loopMode = rawLoopMode;\n }\n else {\n loopMode = 'loop'; // Default to 'loop' for backward compatibility\n }\n let playbackDirection = 1; // 1 = forward, -1 = backward (for pingpong mode)\n console.log('[StorySplat Viewer] Playback config:', {\n loopMode,\n playbackSpeed,\n totalDuration,\n autoPlay: config.autoPlay,\n rawLoopMode: config.loopMode\n });\n // Damped camera interpolation (matching Babylon's pullStrength behavior)\n let targetCameraPosition: pc.Vec3 | null = null;\n let targetCameraRotation: pc.Quat | null = null;\n // PULL_STRENGTH controls camera smoothness during playback\n // HTML export formula: scrollInterpolationSpeed = 0.01 + transitionSpeed * 0.1\n // With transitionSpeed = 0.5: 0.06 (smoother), transitionSpeed = 1: 0.11, transitionSpeed = 2: 0.21 (snappier)\n const PULL_STRENGTH = 0.01 + (config.transitionSpeed || 1) * 0.1;\n // Elastic user rotation (allows user to look around in tour mode, pulls back to target)\n // Using Euler angles (yaw/pitch) instead of quaternion accumulation to prevent roll drift\n let userYawOffset = 0; // Accumulated yaw offset in degrees\n let userPitchOffset = 0; // Accumulated pitch offset in degrees\n const MAX_PITCH_OFFSET = 60; // Limit pitch to prevent flipping\n let isUserDragging = false;\n let lastPointerX = 0;\n let lastPointerY = 0;\n const USER_ROTATION_SENSITIVITY = 0.3; // How responsive rotation is to drag\n const USER_ROTATION_DECAY = 0.95; // How fast rotation returns to target (0.95 = slow, 0.8 = fast)\n // Pre-calculate waypoint positions, rotations, and FOVs for interpolation\n const waypointPositions: pc.Vec3[] = [];\n const waypointRotations: pc.Quat[] = [];\n const waypointFOVs: number[] = [];\n const defaultFOV = config.fov || 60;\n let targetFOV = defaultFOV;\n if (config.waypoints && config.waypoints.length > 0) {\n config.waypoints.forEach(wp => {\n const pos = wp.position\n ? new pc.Vec3(wp.position.x, wp.position.y, -wp.position.z)\n : new pc.Vec3(0, config.playerHeight || 1.6, 0);\n waypointPositions.push(pos);\n const rot = wp.rotation\n ? convertWaypointRotation(wp.rotation)\n : new pc.Quat();\n waypointRotations.push(rot);\n // Extract FOV from waypoint (convert from radians if needed)\n let fov = wp.fov || defaultFOV;\n if (fov < 3.5) {\n // FOV is in radians, convert to degrees\n fov = fov * (180 / Math.PI);\n }\n waypointFOVs.push(fov);\n });\n // Initialize target position/rotation/FOV immediately for damping to work on load\n if (waypointPositions.length > 0) {\n targetCameraPosition = waypointPositions[0].clone();\n targetCameraRotation = waypointRotations[0].clone();\n targetFOV = waypointFOVs[0];\n }\n }\n // Update camera based on progress (sets targets for damped interpolation)\n function updateCameraFromProgress(progress: number): void {\n if (!waypointControlEnabled || waypointPositions.length < 2)\n return;\n progress = Math.max(0, Math.min(1, progress));\n const numWaypoints = waypointPositions.length;\n // Calculate which segment we're in\n const segmentProgress = progress * (numWaypoints - 1);\n const segmentIndex = Math.min(Math.floor(segmentProgress), numWaypoints - 2);\n const t = segmentProgress - segmentIndex;\n // Calculate target position (for damped interpolation)\n const startPos = waypointPositions[segmentIndex];\n const endPos = waypointPositions[segmentIndex + 1];\n targetCameraPosition = new pc.Vec3(lerp(startPos.x, endPos.x, t), lerp(startPos.y, endPos.y, t), lerp(startPos.z, endPos.z, t));\n // Calculate target rotation (for damped interpolation)\n const startRot = waypointRotations[segmentIndex];\n const endRot = waypointRotations[segmentIndex + 1];\n targetCameraRotation = new pc.Quat();\n targetCameraRotation.slerp(startRot, endRot, t);\n // Calculate target FOV (for damped interpolation)\n const startFOV = waypointFOVs[segmentIndex];\n const endFOV = waypointFOVs[segmentIndex + 1];\n targetFOV = lerp(startFOV, endFOV, t);\n // Update waypoint index and emit event if changed\n const newIndex = Math.round(progress * (numWaypoints - 1));\n if (newIndex !== currentWaypointIndex) {\n const prevIndex = currentWaypointIndex;\n currentWaypointIndex = newIndex;\n // Get current waypoint's camera mode\n const currentWaypoint = config.waypoints![newIndex];\n const waypointCameraMode = currentWaypoint.cameraMode || 'first-person';\n // Update orbit mode state based on per-waypoint camera mode\n if (waypointCameraMode === 'orbit') {\n // Enable orbit-style controls while on tour path\n currentWaypointOrbitEnabled = true;\n // Set orbit target (waypoint position or custom target)\n currentWaypointOrbitTarget = currentWaypoint.orbitTarget\n ? new pc.Vec3(currentWaypoint.orbitTarget.x, currentWaypoint.orbitTarget.y, -(currentWaypoint.orbitTarget.z || 0) // Negate Z for coordinate conversion\n )\n : new pc.Vec3(waypointPositions[newIndex].x, waypointPositions[newIndex].y, waypointPositions[newIndex].z);\n }\n else {\n currentWaypointOrbitEnabled = false;\n currentWaypointOrbitTarget = null;\n }\n events.emit('waypointChange', {\n index: newIndex,\n waypoint: currentWaypoint,\n prevIndex,\n cameraMode: waypointCameraMode\n });\n // Update waypoint audio (play/stop based on waypoint entry/exit)\n updateWaypointAudio(newIndex, prevIndex);\n // Close any open hotspot popup and stop all hotspot audio on waypoint change\n stopAllHotspotAudio();\n stopHotspotPopupMedia(container);\n const popupEl = container.querySelector('.storysplat-hotspot-popup') as HTMLElement;\n if (popupEl)\n popupEl.classList.remove('visible');\n }\n // Emit progress event for UI (ensure clamped value)\n events.emit('progressUpdate', { progress: Math.max(0, Math.min(1, progress)), index: currentWaypointIndex });\n // Update splat swap system based on progress\n updateSplats();\n }\n // Apply damped camera interpolation (matching Babylon's pullStrength behavior)\n // Only active in tour mode - explore/walk modes control the camera directly\n function updateCameraDamping(): void {\n if (!waypointControlEnabled)\n return; // Don't interfere with explore/walk mode cameras\n // Interpolate progress toward target (creates scroll inertia - matching BabylonJS HTML export)\n // Skip during button animations to prevent fighting with animateToProgress easing\n if (!isAnimatingProgress) {\n // BabylonJS formula: scrollPosition += (scrollTarget - scrollPosition) * scrollInterpolationSpeed\n const progressDiff = targetProgress - currentProgress;\n if (Math.abs(progressDiff) > 0.0001) {\n currentProgress += progressDiff * PULL_STRENGTH;\n updateCameraFromProgress(currentProgress);\n }\n }\n if (!targetCameraPosition || !targetCameraRotation)\n return;\n // Lerp position toward target (matching Babylon's 0.1 pullStrength)\n const currentPos = camera.getPosition();\n const newPos = new pc.Vec3();\n newPos.lerp(currentPos, targetCameraPosition, PULL_STRENGTH);\n camera.setPosition(newPos.x, newPos.y, newPos.z);\n // Lerp FOV toward target for smooth transitions between waypoints\n const cameraComponent = camera.camera;\n if (cameraComponent && waypointFOVs.length > 0) {\n const currentFOV = cameraComponent.fov;\n const newFOV = lerp(currentFOV, targetFOV, PULL_STRENGTH);\n cameraComponent.fov = newFOV;\n }\n // Decay user rotation offset when not dragging (elastic pull back to target)\n if (!isUserDragging) {\n userYawOffset *= USER_ROTATION_DECAY;\n userPitchOffset *= USER_ROTATION_DECAY;\n // Snap to zero if very small to avoid floating point drift\n if (Math.abs(userYawOffset) < 0.01)\n userYawOffset = 0;\n if (Math.abs(userPitchOffset) < 0.01)\n userPitchOffset = 0;\n }\n // Build user rotation quaternion from Euler angles (no roll)\n const userRotationOffset = new pc.Quat();\n userRotationOffset.setFromEulerAngles(userPitchOffset, userYawOffset, 0);\n // Combine target rotation with user offset for final rotation target\n const targetWithUserOffset = new pc.Quat();\n targetWithUserOffset.mul2(targetCameraRotation, userRotationOffset);\n // Slerp rotation toward combined target\n const currentRot = camera.getRotation();\n const newRot = new pc.Quat();\n newRot.slerp(currentRot, targetWithUserOffset, PULL_STRENGTH);\n camera.setRotation(newRot);\n }\n // Register camera damping in the render loop\n app.on('update', updateCameraDamping);\n // Set progress directly (for button navigation and API calls)\n function setProgress(progress: number, animate = false): void {\n const clampedProgress = Math.max(0, Math.min(1, progress));\n targetProgress = clampedProgress; // Always update target to keep inertia system in sync\n if (animate) {\n animateToProgress(clampedProgress);\n }\n else {\n currentProgress = clampedProgress; // Snap immediately for non-animated calls\n updateCameraFromProgress(currentProgress);\n }\n }\n // Base transition duration (ms) - can be scaled by transitionSpeed setting\n // transitionSpeed multiplies the base duration (0.5 = faster/250ms, 2 = slower/1000ms)\n // This matches the HTML export where higher transitionSpeed = smoother/slower transitions\n const baseTransitionDuration = 500;\n const transitionDuration = baseTransitionDuration * (config.transitionSpeed || 1);\n // Animate to a target progress\n function animateToProgress(animationTarget: number, duration = transitionDuration): void {\n const startProgress = currentProgress;\n const startTime = performance.now();\n isAnimatingProgress = true; // Prevent inertia from interfering\n const animate = () => {\n const elapsed = performance.now() - startTime;\n const t = Math.min(elapsed / duration, 1);\n const eased = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;\n currentProgress = startProgress + (animationTarget - startProgress) * eased;\n updateCameraFromProgress(currentProgress);\n if (t < 1) {\n requestAnimationFrame(animate);\n }\n else {\n isAnimatingProgress = false; // Animation complete, re-enable inertia\n }\n };\n requestAnimationFrame(animate);\n }\n // Navigation functions\n function goToWaypoint(index: number): void {\n if (!config.waypoints || index < 0 || index >= config.waypoints.length)\n return;\n if (!waypointControlEnabled)\n return;\n const waypointProgress = index / Math.max(1, config.waypoints.length - 1);\n targetProgress = waypointProgress; // Update module-level target for inertia system\n animateToProgress(waypointProgress);\n }\n function nextWaypoint(): void {\n if (!config.waypoints || config.waypoints.length === 0)\n return;\n // scrollButtonMode: 'waypoint' jumps to next waypoint, 'percentage' scrolls by scrollAmount\n const buttonMode = config.scrollButtonMode || 'waypoint';\n if (buttonMode === 'percentage' || buttonMode === 'continuous' || buttonMode === 'incremental') {\n // Percentage mode: scroll by scrollAmount percentage\n const scrollAmountSetting = config.scrollAmount || 10;\n const increment = scrollAmountSetting / 100; // Convert percentage to 0-1\n let newProgress = currentProgress + increment;\n // Handle loop mode for percentage-based navigation\n if (newProgress > 1) {\n if (loopMode === 'loop') {\n newProgress = 0; // Wrap to beginning\n }\n else {\n newProgress = 1; // Clamp to end\n }\n }\n targetProgress = newProgress; // Update module-level target for inertia system\n animateToProgress(newProgress);\n }\n else {\n // Waypoint mode: jump to next waypoint\n let next = currentWaypointIndex + 1;\n // Handle loop mode for waypoint-based navigation\n if (next >= config.waypoints.length) {\n if (loopMode === 'loop') {\n next = 0; // Wrap to first waypoint\n }\n else {\n next = config.waypoints.length - 1; // Stay at last waypoint\n }\n }\n goToWaypoint(next);\n }\n }\n function prevWaypoint(): void {\n if (!config.waypoints || config.waypoints.length === 0)\n return;\n // scrollButtonMode: 'waypoint' jumps to prev waypoint, 'percentage' scrolls by scrollAmount\n const buttonMode = config.scrollButtonMode || 'waypoint';\n if (buttonMode === 'percentage' || buttonMode === 'continuous' || buttonMode === 'incremental') {\n // Percentage mode: scroll by scrollAmount percentage\n const scrollAmountSetting = config.scrollAmount || 10;\n const increment = scrollAmountSetting / 100; // Convert percentage to 0-1\n let newProgress = currentProgress - increment;\n // Handle loop mode for percentage-based navigation\n if (newProgress < 0) {\n if (loopMode === 'loop') {\n newProgress = 1; // Wrap to end\n }\n else {\n newProgress = 0; // Clamp to start\n }\n }\n targetProgress = newProgress; // Update module-level target for inertia system\n animateToProgress(newProgress);\n }\n else {\n // Waypoint mode: jump to prev waypoint\n let prev = currentWaypointIndex - 1;\n // Handle loop mode for waypoint-based navigation\n if (prev < 0) {\n if (loopMode === 'loop') {\n prev = config.waypoints.length - 1; // Wrap to last waypoint\n }\n else {\n prev = 0; // Stay at first waypoint\n }\n }\n goToWaypoint(prev);\n }\n }\n // Continuous playback using requestAnimationFrame\n let lastPlaybackTime = 0;\n let playbackAnimationId: number | null = null;\n function playbackLoop(timestamp: number): void {\n if (!isPlaying)\n return;\n if (lastPlaybackTime === 0)\n lastPlaybackTime = timestamp;\n const deltaTime = (timestamp - lastPlaybackTime) / 1000;\n lastPlaybackTime = timestamp;\n // Increment/decrement progress based on direction\n // Update both currentProgress and targetProgress to keep inertia system in sync\n const progressIncrement = playbackSpeed * deltaTime * playbackDirection;\n currentProgress += progressIncrement;\n targetProgress += progressIncrement;\n // Handle end of tour based on loop mode\n if (currentProgress >= 1) {\n console.log('[StorySplat Viewer] End of tour reached, loopMode:', loopMode);\n switch (loopMode) {\n case 'loop':\n console.log('[StorySplat Viewer] Looping - restarting at beginning');\n currentProgress = 0; // Restart at beginning\n targetProgress = 0;\n break;\n case 'pingpong':\n console.log('[StorySplat Viewer] Pingpong - reversing direction');\n currentProgress = 1;\n targetProgress = 1;\n playbackDirection = -1; // Reverse direction\n break;\n case 'none':\n console.log('[StorySplat Viewer] No loop - stopping playback');\n currentProgress = 1;\n targetProgress = 1;\n pause(); // Stop playback\n events.emit('playbackComplete');\n return;\n default:\n console.log('[StorySplat Viewer] Unknown loopMode, stopping:', loopMode);\n currentProgress = 1;\n targetProgress = 1;\n pause(); // Stop playback\n events.emit('playbackComplete');\n return;\n }\n }\n else if (currentProgress <= 0) {\n // Only relevant for pingpong mode going backward\n switch (loopMode) {\n case 'pingpong':\n console.log('[StorySplat Viewer] Pingpong - reversing to forward');\n currentProgress = 0;\n targetProgress = 0;\n playbackDirection = 1; // Reverse back to forward\n break;\n default:\n currentProgress = 0;\n targetProgress = 0;\n break;\n }\n }\n updateCameraFromProgress(currentProgress);\n playbackAnimationId = requestAnimationFrame(playbackLoop);\n }\n function play(): void {\n // 4DGS frame sequence playback\n if (frameSequencePlayer) {\n frameSequencePlayer.play();\n events.emit('playbackStart');\n return;\n }\n // Waypoint playback\n if (isPlaying || !config.waypoints || config.waypoints.length < 2)\n return;\n isPlaying = true;\n lastPlaybackTime = 0;\n playbackDirection = 1; // Always start going forward\n events.emit('playbackStart');\n playbackAnimationId = requestAnimationFrame(playbackLoop);\n }\n function pause(): void {\n // 4DGS frame sequence pause\n if (frameSequencePlayer) {\n frameSequencePlayer.pause();\n events.emit('playbackStop');\n return;\n }\n // Waypoint pause\n isPlaying = false;\n if (playbackAnimationId) {\n cancelAnimationFrame(playbackAnimationId);\n playbackAnimationId = null;\n }\n events.emit('playbackStop');\n }\n function stop(): void {\n // 4DGS frame sequence stop\n if (frameSequencePlayer) {\n frameSequencePlayer.stop();\n events.emit('playbackStop');\n return;\n }\n // Waypoint stop\n pause();\n setProgress(0);\n }\n // Scroll wheel support\n const scrollCanvas = app.graphicsDevice.canvas as HTMLCanvasElement;\n scrollCanvas.addEventListener('wheel', (e: WheelEvent) => {\n if (!waypointControlEnabled)\n return;\n e.preventDefault();\n // Adjust progress based on scroll using configurable speed and amount\n // Match legacy BabylonJS behavior where scroll speed varies with waypoint count\n const scrollSpeedSetting = config.scrollSpeed || 0.1;\n const scrollAmountSetting = config.scrollAmount || 100;\n // Legacy formula: scrollTarget += deltaY * scrollSpeed (where scrollTarget is 0 to pathLength-1)\n // pathLength = (numWaypoints - 1) * 20 for Catmull-Rom spline with 20 subdivisions\n // Convert to 0-1 progress: progressIncrement = (deltaY * scrollSpeed) / pathLength\n const numWaypoints = config.waypoints?.length || 2;\n const effectivePathLength = Math.max(20, (numWaypoints - 1) * 20); // Min 20 to avoid division issues\n const normalizedDelta = Math.abs(e.deltaY) / 100; // Normalize wheel delta\n const baseIncrement = (normalizedDelta * 100 * scrollSpeedSetting * (scrollAmountSetting / 100)) / effectivePathLength;\n const scrollIncrement = e.deltaY > 0 ? baseIncrement : -baseIncrement;\n // Update target progress - the update loop will lerp currentProgress toward it (creates inertia)\n targetProgress = Math.max(0, Math.min(1, targetProgress + scrollIncrement));\n }, { passive: false });\n // Elastic user rotation - track pointer drag for look-around in tour mode\n // Use capture phase to get events before PlayCanvas input system\n scrollCanvas.addEventListener('pointerdown', (e: PointerEvent) => {\n if (!waypointControlEnabled)\n return; // Only in tour/waypoint mode\n isUserDragging = true;\n lastPointerX = e.clientX;\n lastPointerY = e.clientY;\n }, { capture: true });\n scrollCanvas.addEventListener('pointermove', (e: PointerEvent) => {\n if (!waypointControlEnabled || !isUserDragging)\n return;\n const deltaX = e.clientX - lastPointerX;\n const deltaY = e.clientY - lastPointerY;\n lastPointerX = e.clientX;\n lastPointerY = e.clientY;\n // Convert drag delta to rotation offset in degrees (yaw and pitch only, no roll)\n const yawDelta = -deltaX * USER_ROTATION_SENSITIVITY;\n const pitchDelta = -deltaY * USER_ROTATION_SENSITIVITY;\n // Accumulate yaw and pitch offsets (Euler angles prevent roll drift)\n userYawOffset += yawDelta;\n userPitchOffset += pitchDelta;\n // Clamp pitch to prevent camera flipping\n userPitchOffset = Math.max(-MAX_PITCH_OFFSET, Math.min(MAX_PITCH_OFFSET, userPitchOffset));\n }, { capture: true });\n scrollCanvas.addEventListener('pointerup', () => {\n isUserDragging = false;\n }, { capture: true });\n scrollCanvas.addEventListener('pointerleave', () => {\n isUserDragging = false;\n }, { capture: true });\n // =====================================================\n // HOTSPOT SYSTEM\n // =====================================================\n const hotspotEntities: ViewerRuntimeEntity[] = [];\n // =====================================================\n // PORTAL SYSTEM (Scene-to-Scene Navigation)\n // =====================================================\n const portalEntities: ViewerRuntimeEntity[] = [];\n // Portal confirmation popup (optional)\n const portalPopupEl = (uiElements as UIElementsWithPortalPopup).portalPopup;\n let pendingPortal: PortalData | null = null;\n const hidePortalPopup = () => {\n if (!portalPopupEl)\n return;\n portalPopupEl.classList.remove('visible');\n pendingPortal = null;\n };\n const showPortalPopup = (portal: PortalData) => {\n if (!portalPopupEl)\n return;\n const titleEl = portalPopupEl.querySelector('.storysplat-portal-popup-title') as HTMLElement | null;\n if (titleEl) {\n if (portal.title) {\n titleEl.textContent = portal.title;\n }\n else if (portal.targetSceneName) {\n titleEl.textContent = `${getButtonLabel(currentButtonLabels, 'switchScenes').replace('?', '')} ${portal.targetSceneName}?`;\n }\n else {\n titleEl.textContent = getButtonLabel(currentButtonLabels, 'switchScenes');\n }\n }\n pendingPortal = portal;\n portalPopupEl.classList.add('visible');\n };\n if (portalPopupEl) {\n const confirmBtn = portalPopupEl.querySelector('.storysplat-portal-popup-confirm') as HTMLElement | null;\n const cancelBtn = portalPopupEl.querySelector('.storysplat-portal-popup-cancel') as HTMLElement | null;\n confirmBtn?.addEventListener('click', () => {\n if (pendingPortal) {\n const portal = pendingPortal;\n hidePortalPopup();\n handlePortalNavigation(portal);\n }\n });\n cancelBtn?.addEventListener('click', () => {\n hidePortalPopup();\n });\n }\n // Parse hex color to PlayCanvas Color\n function parseColor(hex: string): pc.Color {\n const r = parseInt(hex.slice(1, 3), 16) / 255;\n const g = parseInt(hex.slice(3, 5), 16) / 255;\n const b = parseInt(hex.slice(5, 7), 16) / 255;\n return new pc.Color(r, g, b);\n }\n // =====================================================\n // SPATIAL AUDIO SYSTEM\n // =====================================================\n interface HotspotAudioElements {\n audio: HTMLAudioElement;\n audioCtx?: AudioContext;\n source?: MediaElementAudioSourceNode;\n panner?: PannerNode;\n updateAudioPosition?: () => void;\n }\n interface VideoSpatialAudio {\n audioCtx: AudioContext;\n source: MediaElementAudioSourceNode;\n panner: PannerNode;\n }\n interface WaypointAudioData {\n entity: pc.Entity;\n waypointIndex: number;\n config: LegacyWaypointAudioConfig;\n slotId: string;\n playing: boolean;\n autoplayTriggered: boolean;\n assetReady: boolean;\n }\n // Track all audio for muting\n const allAudioContexts: AudioContext[] = [];\n const allAudioElements: HTMLAudioElement[] = [];\n let globalMuted = false;\n const storedVolumes = new Map<HTMLAudioElement, number>();\n // Waypoint audio tracking\n const waypointAudioMap = new Map<string, WaypointAudioData>();\n /**\n * Stop all hotspot audio (spatial and non-spatial) and pause any popup media.\n * Called on waypoint transitions and when closing the hotspot popup.\n */\n function stopAllHotspotAudio(): void {\n hotspotEntities.forEach((entity) => {\n if (entity.audioElements) {\n const audioElements = entity.audioElements as HotspotAudioElements;\n const audio = audioElements.audio;\n if (audio && !audio.paused) {\n audio.pause();\n audio.currentTime = 0;\n }\n }\n // Also stop any video hotspots that are playing\n if (entity.videoElement) {\n const video = entity.videoElement as HTMLVideoElement;\n if (!video.paused) {\n video.pause();\n }\n }\n // Reset proximity state so audio can re-trigger on re-entry\n entity.wasInProximity = false;\n });\n }\n /**\n * Stop any media playing inside the hotspot popup (videos, iframes).\n */\n function stopHotspotPopupMedia(containerEl: HTMLElement): void {\n const popup = containerEl.querySelector('.storysplat-hotspot-popup') as HTMLElement;\n if (!popup)\n return;\n // Pause any popup video elements\n popup.querySelectorAll('video').forEach(v => { v.pause(); v.currentTime = 0; });\n // Clear iframe src to stop YouTube embeds etc\n popup.querySelectorAll('iframe').forEach(iframe => { iframe.src = ''; });\n }\n // =====================================================\n // PARTICLE SYSTEM\n // =====================================================\n const particleEntities: Map<string, pc.Entity> = new Map();\n const particleTextures: Map<string, pc.Texture> = new Map();\n // Particle texture URLs (matching HTML export particleSystem.ts)\n const PARTICLE_TEXTURE_URLS: Record<string, string> = {\n flare: 'https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fparticles%2Fflare.png?alt=media&token=ce114781-2ac3-41b2-b9c2-34bda0f6eb13',\n circle: 'https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fparticles%2Fcircle.png?alt=media&token=fd01b475-2b94-4c24-bc83-2a907045715f',\n spark: 'https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fparticles%2Fsparkle.png?alt=media&token=466739a4-ccd7-4295-88c2-dedd681a34f0',\n rain: 'https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fparticles%2Frain.png?alt=media&token=13478487-6259-4906-838c-ad592db893e4',\n smoke: 'https://firebasestorage.googleapis.com/v0/b/story-splat.firebasestorage.app/o/public%2Fparticles%2Fsmoke.png?alt=media&token=6cece4f8-87cf-47f9-974d-c4dd6a649f0f',\n };\n /**\n * Load a particle texture (with caching)\n */\n function loadParticleTexture(name: string, url: string): Promise<pc.Texture> {\n return new Promise((resolve, reject) => {\n // Check cache first\n if (particleTextures.has(name)) {\n resolve(particleTextures.get(name)!);\n return;\n }\n const asset = new pc.Asset(name, 'texture', { url: url });\n asset.on('load', () => {\n // Check if viewer was destroyed while loading\n if (isDestroyed) {\n console.log(`[Particle] Ignoring texture load - viewer was destroyed: ${name}`);\n reject(new Error('Viewer destroyed'));\n return;\n }\n const texture = asset.resource as pc.Texture;\n particleTextures.set(name, texture);\n resolve(texture);\n });\n asset.on('error', (err: unknown) => {\n console.error(`[Particle] Failed to load texture: ${name}`, err);\n reject(err);\n });\n app.assets.add(asset);\n app.assets.load(asset);\n });\n }\n /**\n * Create a particle system entity (matching HTML export createParticleSystem)\n * Enhanced with full feature parity: direction1/direction2, box emitter positioning,\n * scaleX/Y, colorDead, angular speed range, initial rotation range\n */\n function createParticleSystemEntity(psConfig: LegacyParticleConfig): pc.Entity {\n const entity = new pc.Entity(psConfig.name || 'particle-system');\n // Helper to safely get position coordinates\n const getPos = (vec: LegacyVec3 | undefined, defaultVal = 0) => ({\n x: vec?._x ?? vec?.x ?? defaultVal,\n y: vec?._y ?? vec?.y ?? defaultVal,\n z: vec?._z ?? vec?.z ?? defaultVal\n });\n // Helper to safely get color\n const getColor = (color: LegacyColor | undefined): Required<LegacyColor> => ({\n r: color?.r ?? 1,\n g: color?.g ?? 1,\n b: color?.b ?? 1,\n a: color?.a ?? 1\n });\n const emitterPos = getPos(psConfig.emitterPosition, 0);\n const gravity = getPos(psConfig.gravity, 0);\n const color1 = getColor(psConfig.color1);\n const color2 = getColor(psConfig.color2);\n const colorDead = getColor(psConfig.colorDead);\n // Get direction1/direction2 for directional emission (matching HTML export)\n const direction1 = getPos(psConfig.direction1, 0);\n const direction2 = getPos(psConfig.direction2, 0);\n // Calculate lifetime from min/max\n const minLifeTime = psConfig.minLifeTime ?? 1.0;\n const maxLifeTime = psConfig.maxLifeTime ?? 3.0;\n const lifetime = (minLifeTime + maxLifeTime) / 2;\n // Determine emitter shape and calculate extents\n let emitterShape: number = pc.EMITTERSHAPE_BOX;\n let emitterExtents = new pc.Vec3(0.1, 0.1, 0.1);\n let emitterOffset = new pc.Vec3(0, 0, 0);\n if (psConfig.emitterType === 'sphere' || psConfig.emitterShape === 'sphere') {\n emitterShape = pc.EMITTERSHAPE_SPHERE;\n const radius = psConfig.emitterRadius || 0.1;\n emitterExtents = new pc.Vec3(radius, radius, radius);\n }\n else if (psConfig.emitterShape === 'box' && psConfig.emitBoxMin && psConfig.emitBoxMax) {\n // Box emitter with min/max emit box (matching HTML export)\n emitterShape = pc.EMITTERSHAPE_BOX;\n const boxMin = getPos(psConfig.emitBoxMin, 0);\n const boxMax = getPos(psConfig.emitBoxMax, 0);\n // Calculate extents as half the size of the box\n emitterExtents = new pc.Vec3(Math.abs(boxMax.x - boxMin.x) / 2, Math.abs(boxMax.y - boxMin.y) / 2, Math.abs(boxMax.z - boxMin.z) / 2);\n // Calculate center offset from emitter position\n emitterOffset = new pc.Vec3((boxMin.x + boxMax.x) / 2, (boxMin.y + boxMax.y) / 2, -(boxMin.z + boxMax.z) / 2 // Negate Z for coordinate conversion\n );\n }\n else if (psConfig.emitterShape === 'point') {\n // Point emitter - use very small extents\n emitterShape = pc.EMITTERSHAPE_BOX;\n emitterExtents = new pc.Vec3(0.01, 0.01, 0.01);\n }\n else {\n // Default box emitter\n emitterExtents = new pc.Vec3(psConfig.emitterExtents?.x || psConfig.emitterRadius || 0.1, psConfig.emitterExtents?.y || psConfig.emitterRadius || 0.1, psConfig.emitterExtents?.z || psConfig.emitterRadius || 0.1);\n }\n // Determine blend mode (matching HTML export blendModeMap)\n let blendMode: number = pc.BLEND_ADDITIVEALPHA;\n const blendModeStr = psConfig.blendMode || '';\n if (blendModeStr === 'BLENDMODE_STANDARD' || blendModeStr === 'normal' || blendModeStr === 'alpha') {\n blendMode = pc.BLEND_NORMAL;\n }\n else if (blendModeStr === 'BLENDMODE_MULTIPLY' || blendModeStr === 'multiply') {\n blendMode = pc.BLEND_MULTIPLICATIVE;\n }\n else if (blendModeStr === 'BLENDMODE_ONEONE') {\n blendMode = pc.BLEND_ADDITIVE;\n }\n else if (blendModeStr === 'BLENDMODE_ADD' || blendModeStr === 'BLENDMODE_MULTIPLYADD') {\n blendMode = pc.BLEND_ADDITIVEALPHA;\n }\n // Build world velocity graph with gravity\n const gravityX = gravity.x || 0;\n const gravityY = gravity.y || 0; // Don't default to -9.8, use config value\n const gravityZ = -(gravity.z || 0); // Negate Z for coordinate conversion\n // Calculate accumulated velocity at end of lifetime due to gravity\n const endVelX = gravityX * lifetime;\n const endVelY = gravityY * lifetime;\n const endVelZ = gravityZ * lifetime;\n // Get angular speed range (matching HTML export minAngularSpeed/maxAngularSpeed)\n const minAngularSpeed = psConfig.minAngularSpeed ?? 0;\n const maxAngularSpeed = psConfig.maxAngularSpeed ?? (psConfig.angularSpeed ?? 0);\n // Get initial rotation range (matching HTML export minInitialRotation/maxInitialRotation)\n const minInitialRotation = psConfig.minInitialRotation ?? 0;\n const maxInitialRotation = psConfig.maxInitialRotation ?? 0;\n // Get scale ranges (matching HTML export minScaleX/maxScaleX/minScaleY/maxScaleY)\n const minSize = psConfig.minSize ?? 0.1;\n const maxSize = psConfig.maxSize ?? 0.3;\n const minScaleX = psConfig.minScaleX ?? 1;\n const maxScaleX = psConfig.maxScaleX ?? 1;\n const minScaleY = psConfig.minScaleY ?? 1;\n const maxScaleY = psConfig.maxScaleY ?? 1;\n // Calculate emit power range\n const minEmitPower = psConfig.minEmitPower ?? 1;\n const maxEmitPower = psConfig.maxEmitPower ?? 2;\n // Calculate direction-based velocity (average of direction1 and direction2)\n const avgDirX = (direction1.x + direction2.x) / 2;\n const avgDirY = (direction1.y + direction2.y) / 2;\n const avgDirZ = -(direction1.z + direction2.z) / 2; // Negate Z\n // Create particle system with enhanced parameters (full feature parity)\n entity.addComponent('particlesystem', {\n numParticles: psConfig.numParticles || 500,\n lifetime: lifetime,\n rate: psConfig.emitRate || 50,\n emitterShape: emitterShape,\n emitterExtents: emitterExtents,\n emitterRadius: psConfig.emitterRadius || 0.1,\n // Initial rotation range (matching HTML export)\n startAngle: minInitialRotation,\n startAngle2: maxInitialRotation !== 0 ? maxInitialRotation : 360,\n // Speed (radial velocity)\n radialSpeedGraph: new pc.Curve([0, minEmitPower, 1, maxEmitPower]),\n // Local space velocity (for directional emission using direction1/direction2)\n localVelocityGraph: new pc.CurveSet([\n [0, avgDirX * minEmitPower],\n [0, avgDirY * minEmitPower],\n [0, avgDirZ * minEmitPower]\n ]),\n localVelocityGraph2: new pc.CurveSet([\n [0, avgDirX * maxEmitPower],\n [0, avgDirY * maxEmitPower],\n [0, avgDirZ * maxEmitPower]\n ]),\n // World velocity (gravity effect over lifetime)\n velocityGraph: new pc.CurveSet([\n [0, 0, 1, endVelX],\n [0, 0, 1, endVelY],\n [0, 0, 1, endVelZ]\n ]),\n // Scale over lifetime with X/Y scale support\n scaleGraph: new pc.Curve([0, minSize * minScaleX, 1, maxSize * maxScaleX]),\n scaleGraph2: new pc.Curve([0, minSize * minScaleY, 1, maxSize * maxScaleY]),\n // Rotation speed range over lifetime (matching HTML export minAngularSpeed/maxAngularSpeed)\n rotationSpeedGraph: new pc.Curve([0, minAngularSpeed]),\n rotationSpeedGraph2: maxAngularSpeed !== minAngularSpeed\n ? new pc.Curve([0, maxAngularSpeed])\n : undefined,\n // Color over lifetime with colorDead (RGB) - start -> middle -> dead\n colorGraph: new pc.CurveSet([\n [0, color1.r, 0.5, color2.r, 1, colorDead.r],\n [0, color1.g, 0.5, color2.g, 1, colorDead.g],\n [0, color1.b, 0.5, color2.b, 1, colorDead.b]\n ]),\n // Alpha over lifetime with colorDead alpha\n alphaGraph: new pc.Curve([\n 0, color1.a ?? 1,\n 0.5, color2.a ?? 0.8,\n 1, colorDead.a ?? 0\n ]),\n // Rendering settings\n blend: blendMode,\n depthWrite: psConfig.depthWrite ?? false,\n depthSoftening: psConfig.softParticles ?? 0,\n lighting: psConfig.lighting ?? false,\n halfLambert: psConfig.halfLambert ?? false,\n alignToMotion: psConfig.alignToMotion ?? false,\n stretch: psConfig.stretch || 0,\n preWarm: psConfig.preWarm ?? false,\n loop: psConfig.loop ?? true,\n autoPlay: psConfig.autoPlay ?? true,\n // Sorting and orientation\n sort: psConfig.sort ?? 0,\n orientation: psConfig.orientation ?? 0\n });\n // Set local space mode\n if (entity.particlesystem) {\n entity.particlesystem.localSpace = psConfig.localSpace ?? false;\n }\n // Get translationPivot (BabylonJS Vector2 that offsets particle spawn position)\n const translationPivotX = psConfig.translationPivot?.x ?? 0;\n const translationPivotY = psConfig.translationPivot?.y ?? 0;\n // Set position (negate Z for BabylonJS -> PlayCanvas coordinate conversion)\n // Add emitter offset for box emitter positioning and translationPivot offset\n entity.setPosition(emitterPos.x + emitterOffset.x + translationPivotX, emitterPos.y + emitterOffset.y + translationPivotY, -emitterPos.z + emitterOffset.z);\n // Apply renderingGroupId via layer assignment (BabylonJS uses 0-3 for z-ordering)\n // PlayCanvas uses layers - map renderingGroupId to appropriate layer\n const renderingGroupId = psConfig.renderingGroupId ?? 3;\n if (entity.particlesystem && renderingGroupId !== undefined) {\n // In PlayCanvas, higher drawOrder values render on top\n // Map BabylonJS renderingGroupId (0-3) to PlayCanvas draw order\n entity.particlesystem.drawOrder = renderingGroupId;\n }\n console.log(`[Particle] Entity configured at position: (${emitterPos.x + emitterOffset.x + translationPivotX}, ${emitterPos.y + emitterOffset.y + translationPivotY}, ${-emitterPos.z + emitterOffset.z})`);\n console.log(`[Particle] Emitter shape: ${psConfig.emitterShape || 'box'}, extents: ${emitterExtents.x}, ${emitterExtents.y}, ${emitterExtents.z}`);\n console.log(`[Particle] Gravity: (${gravityX}, ${gravityY}, ${gravityZ})`);\n console.log(`[Particle] Colors: start=${JSON.stringify(color1)}, mid=${JSON.stringify(color2)}, dead=${JSON.stringify(colorDead)}`);\n console.log(`[Particle] Angular speed: ${minAngularSpeed} - ${maxAngularSpeed}, Initial rotation: ${minInitialRotation} - ${maxInitialRotation}`);\n console.log(`[Particle] TranslationPivot: (${translationPivotX}, ${translationPivotY}), RenderingGroupId: ${renderingGroupId}`);\n return entity;\n }\n /**\n * Initialize all particle systems from config\n */\n async function initParticleSystems(): Promise<void> {\n console.log('═══════════════════════════════════════');\n console.log('🎆 PARTICLE SYSTEM INITIALIZATION');\n console.log('═══════════════════════════════════════');\n console.log('[Particle] config.particles:', config.particles);\n console.log('[Particle] Type:', typeof config.particles);\n console.log('[Particle] Is Array:', Array.isArray(config.particles));\n if (!config.particles || config.particles.length === 0) {\n console.log('[Particle] ⚠️ No particle systems to create (particles array is empty or undefined)');\n console.log('═══════════════════════════════════════');\n return;\n }\n console.log(`[Particle] 📋 Found ${config.particles.length} particle system(s) to create`);\n for (let i = 0; i < config.particles.length; i++) {\n const ps = config.particles[i];\n console.log(`[Particle] --- Particle System ${i + 1}/${config.particles.length} ---`);\n console.log('[Particle] Raw config:', JSON.stringify(ps, null, 2));\n try {\n // Get texture URL - support custom textures\n const textureName = ps.particleTexture || 'flare';\n let textureUrl: string;\n let textureCacheKey: string;\n if (textureName === 'custom' && ps.customTextureUrl) {\n // Use custom texture URL\n textureUrl = ps.customTextureUrl;\n textureCacheKey = `custom_${ps.id || ps.name || i}`;\n console.log(`[Particle] Custom texture: ${textureUrl.substring(0, 60)}...`);\n }\n else {\n // Use predefined texture\n textureUrl = PARTICLE_TEXTURE_URLS[textureName] || PARTICLE_TEXTURE_URLS['flare'];\n textureCacheKey = textureName;\n console.log(`[Particle] Texture: ${textureName} -> ${textureUrl.substring(0, 60)}...`);\n }\n // Load texture\n console.log('[Particle] Loading texture...');\n const texture = await loadParticleTexture(textureCacheKey, textureUrl);\n console.log('[Particle] ✅ Texture loaded:', texture.name);\n // Create particle system entity\n console.log('[Particle] Creating entity...');\n const entity = createParticleSystemEntity(ps);\n console.log('[Particle] ✅ Entity created:', entity.name);\n // Set the texture on the particle system\n if (entity.particlesystem) {\n entity.particlesystem.colorMap = texture;\n console.log('[Particle] ✅ Texture applied to particle system');\n console.log('[Particle] Particle system properties:', {\n numParticles: entity.particlesystem.numParticles,\n lifetime: entity.particlesystem.lifetime,\n rate: entity.particlesystem.rate,\n loop: entity.particlesystem.loop,\n autoPlay: entity.particlesystem.autoPlay\n });\n }\n else {\n console.warn('[Particle] ⚠️ Entity has no particlesystem component!');\n }\n // Add to scene\n app.root.addChild(entity);\n console.log('[Particle] ✅ Entity added to scene');\n // Log entity position\n const pos = entity.getPosition();\n console.log(`[Particle] Position: (${pos.x.toFixed(2)}, ${pos.y.toFixed(2)}, ${pos.z.toFixed(2)})`);\n // Store reference\n const safeName = (ps.id || ps.name || `particle-${particleEntities.size}`).replace(/[^a-zA-Z0-9]/g, '_');\n particleEntities.set(safeName, entity);\n console.log(`[Particle] ✅ SUCCESS: Created \"${ps.name || safeName}\"`);\n }\n catch (err: unknown) {\n const errObj = err instanceof Error ? err : { message: String(err), stack: '' };\n console.error(`[Particle] ❌ FAILED to create particle system: ${ps.name}`);\n console.error(`[Particle] Error message: ${errObj.message || 'No message'}`);\n console.error(`[Particle] Error stack: ${errObj.stack || 'No stack'}`);\n console.error(`[Particle] Error object:`, err);\n }\n }\n console.log('═══════════════════════════════════════');\n console.log(`[Particle] 🎉 COMPLETE: ${particleEntities.size}/${config.particles.length} particle systems created`);\n console.log('[Particle] Active particle systems:', Array.from(particleEntities.keys()));\n console.log('═══════════════════════════════════════');\n }\n /**\n * Stop all particle systems\n */\n function stopAllParticles(): void {\n particleEntities.forEach((entity) => {\n if (entity.particlesystem) {\n entity.particlesystem.stop();\n }\n });\n }\n /**\n * Start all particle systems\n */\n function startAllParticles(): void {\n particleEntities.forEach((entity) => {\n if (entity.particlesystem) {\n entity.particlesystem.play();\n }\n });\n }\n /**\n * Toggle a specific particle system by ID\n */\n function toggleParticle(id: string): void {\n const entity = particleEntities.get(id);\n if (entity) {\n entity.enabled = !entity.enabled;\n }\n }\n // =====================================================\n // CUSTOM MESH SYSTEM (3D Models - GLB/GLTF)\n // =====================================================\n interface AnimBaseLayerLike {\n states?: string[];\n play?: (state?: string) => void;\n }\n interface AnimComponentLike {\n playing?: boolean;\n speed?: number;\n loop?: boolean;\n baseLayer?: AnimBaseLayerLike;\n animations?: Record<string, unknown>;\n play?: (clip?: string, blendTime?: number) => void;\n pause?: () => void;\n loadStateGraph?: (stateGraph: unknown) => void;\n assignAnimation?: (path: string, asset: unknown, layer?: string) => void;\n }\n interface AnimInfo {\n type: 'pc-anim' | 'anim' | 'animation' | 'glb-skeleton';\n component?: AnimComponentLike;\n node?: pc.Entity;\n modelEntity?: AnimatedEntity;\n asset?: pc.Asset;\n animations?: AnimationResourceLike[];\n animationNames?: string[];\n animComponent?: AnimComponentLike;\n isPlaying?: boolean;\n startTime?: number;\n updateHandler?: ((dt: number) => void) | null;\n currentTime?: number;\n }\n type AnimatedEntity = Omit<pc.Entity, 'anim' | 'animation'> & {\n anim?: AnimComponentLike;\n animation?: AnimComponentLike;\n };\n interface AnimationResourceLike {\n name?: string;\n resource?: {\n name?: string;\n _name?: string;\n };\n _name?: string;\n _duration?: number;\n duration?: number;\n _curves?: Array<{\n evaluate?: (time: number) => unknown;\n } | null>;\n _paths?: Array<{\n entityPath?: string[];\n component?: string;\n propertyPath?: string[];\n }>;\n }\n interface ContainerResourceWithAnimations extends pc.ContainerResource {\n animations?: AnimationResourceLike[];\n }\n interface CustomMeshData {\n entity: pc.Entity;\n config: CustomMeshConfigLike;\n animComponent?: AnimComponentLike;\n allAnimComponents?: AnimInfo[];\n audioSlotId?: string;\n isAnimPlaying: boolean;\n audioPlaying: boolean;\n }\n const customMeshEntities: Map<string, CustomMeshData> = new Map();\n /**\n * Helper to play an animation component using various methods\n * PlayCanvas has different ways to start animations depending on the setup\n */\n function playAnimComponent(anim: AnimComponentLike): void {\n try {\n // Method 1: Set playing property (simple animations)\n anim.playing = true;\n // Method 2: Use baseLayer.play() if available (Anim component)\n if (anim.baseLayer && typeof anim.baseLayer.play === 'function') {\n // Get the first available state/clip name\n const states = anim.baseLayer.states;\n if (states && states.length > 0) {\n const firstState = states[0];\n if (firstState && firstState !== 'START' && firstState !== 'END' && firstState !== 'ANY') {\n anim.baseLayer.play(firstState);\n console.log('[CustomMesh] Playing animation state:', firstState);\n }\n }\n }\n // Method 3: Direct play() method\n if (typeof anim.play === 'function') {\n anim.play();\n }\n // Method 4: Set speed to ensure animation runs\n if (anim.speed !== undefined) {\n anim.speed = 1;\n }\n console.log('[CustomMesh] Animation started, playing:', anim.playing);\n }\n catch (err) {\n console.error('[CustomMesh] Error playing animation:', err);\n }\n }\n /**\n * Helper to pause an animation component\n */\n function pauseAnimComponent(anim: AnimComponentLike): void {\n try {\n anim.playing = false;\n if (anim.speed !== undefined) {\n anim.speed = 0;\n }\n if (typeof anim.pause === 'function') {\n anim.pause();\n }\n }\n catch (err) {\n console.error('[CustomMesh] Error pausing animation:', err);\n }\n }\n /**\n * Sample an animation track at a specific time\n * Handles keyframe interpolation for position, rotation, and scale\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n function sampleAnimationTrack(track: Record<string, any>, time: number): unknown {\n try {\n const keys: Record<string, any>[] = track.keys || track.keyframes || [];\n if (keys.length === 0)\n return null;\n // Find surrounding keyframes\n let prevKey = keys[0];\n let nextKey = keys[keys.length - 1];\n for (let i = 0; i < keys.length - 1; i++) {\n const currentTime = keys[i].time ?? keys[i][0];\n const nextTime = keys[i + 1].time ?? keys[i + 1][0];\n if (time >= currentTime && time < nextTime) {\n prevKey = keys[i];\n nextKey = keys[i + 1];\n break;\n }\n }\n // Calculate interpolation factor\n const prevTime = prevKey.time ?? prevKey[0] ?? 0;\n const nextTime = nextKey.time ?? nextKey[0] ?? 0;\n const duration = nextTime - prevTime;\n const t = duration > 0 ? (time - prevTime) / duration : 0;\n // Get values (handle both object and array formats)\n const prevValue = prevKey.value ?? prevKey.slice?.(1) ?? prevKey;\n const nextValue = nextKey.value ?? nextKey.slice?.(1) ?? nextKey;\n // Interpolate based on track type\n const trackType: string = track.type || track.property || 'position';\n if (trackType.includes('rotation') || trackType.includes('quaternion')) {\n // Quaternion SLERP interpolation\n if (Array.isArray(prevValue) && prevValue.length >= 4) {\n return slerpQuat(prevValue, nextValue, t);\n }\n return prevValue;\n }\n else {\n // Linear interpolation for position/scale\n if (Array.isArray(prevValue)) {\n return prevValue.map((v: number, i: number) => {\n const nv = nextValue[i] ?? v;\n return v + (nv - v) * t;\n });\n }\n return prevValue;\n }\n }\n catch (e) {\n return null;\n }\n }\n /**\n * Simple quaternion SLERP\n */\n function slerpQuat(a: number[], b: number[], t: number): number[] {\n // Calculate cosine of angle\n let cosom = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];\n // Adjust signs if necessary\n const bAdj = [...b];\n if (cosom < 0) {\n cosom = -cosom;\n bAdj[0] = -bAdj[0];\n bAdj[1] = -bAdj[1];\n bAdj[2] = -bAdj[2];\n bAdj[3] = -bAdj[3];\n }\n // Calculate coefficients\n let scale0: number, scale1: number;\n if (1 - cosom > 0.0001) {\n const omega = Math.acos(cosom);\n const sinom = Math.sin(omega);\n scale0 = Math.sin((1 - t) * omega) / sinom;\n scale1 = Math.sin(t * omega) / sinom;\n }\n else {\n // Use linear interpolation for very close quaternions\n scale0 = 1 - t;\n scale1 = t;\n }\n return [\n scale0 * a[0] + scale1 * bAdj[0],\n scale0 * a[1] + scale1 * bAdj[1],\n scale0 * a[2] + scale1 * bAdj[2],\n scale0 * a[3] + scale1 * bAdj[3]\n ];\n }\n /**\n * Apply an animation value to a target entity\n */\n function applyAnimationValue(rootEntity: pc.Entity, targetPath: string, trackType: string, value: unknown): void {\n try {\n // Parse the target path (e.g., \"Bone001/position\" or just \"position\")\n const parts = targetPath.split('/');\n const property = parts.pop() || targetPath;\n // Find the target node\n let target: pc.Entity | null = rootEntity;\n if (parts.length > 0) {\n const nodeName = parts.join('/');\n target = rootEntity.findByName(nodeName) as pc.Entity || rootEntity;\n }\n if (!target)\n return;\n // Apply the value based on property type\n const normalizedProp = property.toLowerCase();\n if (normalizedProp.includes('position') || normalizedProp.includes('translation')) {\n if (Array.isArray(value) && value.length >= 3) {\n target.setLocalPosition(value[0], value[1], value[2]);\n }\n }\n else if (normalizedProp.includes('rotation') || normalizedProp.includes('quaternion')) {\n if (Array.isArray(value) && value.length >= 4) {\n target.setLocalRotation(new pc.Quat(value[0], value[1], value[2], value[3]));\n }\n else if (Array.isArray(value) && value.length >= 3) {\n // Euler angles\n target.setLocalEulerAngles(value[0], value[1], value[2]);\n }\n }\n else if (normalizedProp.includes('scale')) {\n if (Array.isArray(value) && value.length >= 3) {\n target.setLocalScale(value[0], value[1], value[2]);\n }\n }\n }\n catch (e) {\n // Silently ignore - animation track may reference non-existent node\n }\n }\n /**\n * Enhanced animation player that handles both anim and animation components\n * Also handles the new format with type info including GLB animations\n */\n function playAnimComponentV2(animInfo: AnimInfo): void {\n const { type, component, node, animations } = animInfo;\n console.log('[CustomMesh] Playing animation, type:', type, 'node:', node?.name, 'animations:', animations?.length);\n try {\n if (type === 'pc-anim') {\n // Simple PlayCanvas anim component - matches HTML export approach\n console.log('[CustomMesh] Using simple pc-anim approach (like HTML export)');\n if (!component) return;\n component.playing = true;\n animInfo.isPlaying = true;\n console.log('[CustomMesh] Animation started - playing:', component.playing);\n }\n else if (type === 'animation') {\n // Legacy Animation component\n if (!component) return;\n component.playing = true;\n component.loop = true;\n if (typeof component.play === 'function') {\n // Play the first animation clip\n const clips = Object.keys(component.animations || {});\n if (clips.length > 0) {\n component.play(clips[0], 1);\n console.log('[CustomMesh] Playing legacy animation clip:', clips[0]);\n }\n else {\n component.play();\n }\n }\n }\n else if (type === 'glb-skeleton') {\n // GLB skeleton animation - use PlayCanvas AnimComponent\n console.log('[CustomMesh] Starting GLB skeleton animation playback');\n const { modelEntity, animations: animList } = animInfo;\n if (!modelEntity) return;\n if (animList && animList.length > 0) {\n try {\n // Add AnimComponent if not exists\n if (!modelEntity.anim) {\n modelEntity.addComponent('anim', {\n activate: true,\n speed: 1\n });\n }\n if (modelEntity.anim) {\n const animComp = modelEntity.anim as unknown as AnimComponentLike;\n // Assign each animation from the container\n // containerResource.animations are Asset objects — .resource is the AnimTrack\n animList.forEach((animAsset: AnimationResourceLike, i: number) => {\n const animTrack = animAsset.resource || animAsset;\n const animName = animTrack._name || animTrack.name || animAsset._name || animAsset.name || `Anim_${i}`;\n console.log('[CustomMesh] Assigning animation track:', animName, 'type:', typeof animTrack);\n try {\n animComp.assignAnimation?.(animName, animTrack);\n } catch (assignErr) {\n console.warn('[CustomMesh] assignAnimation failed for', animName, assignErr);\n }\n });\n // Play the animation\n modelEntity.anim.playing = true;\n modelEntity.anim.speed = 1;\n animInfo.animComponent = modelEntity.anim;\n animInfo.isPlaying = true;\n console.log('[CustomMesh] GLB animation playing via AnimComponent');\n }\n } catch (err) {\n console.error('[CustomMesh] Error setting up GLB animation:', err);\n }\n }\n }\n else if (type === 'anim' && component) {\n // New Anim component\n component.playing = true;\n component.speed = 1;\n // Try to play via baseLayer\n if (component.baseLayer) {\n const states = component.baseLayer.states || [];\n const playableState = states.find((s: string) => s !== 'START' && s !== 'END' && s !== 'ANY');\n if (playableState) {\n component.baseLayer.play?.(playableState);\n console.log('[CustomMesh] Playing anim state:', playableState);\n }\n }\n }\n // Only log component state if component exists (not for glb-skeleton)\n if (component) {\n console.log('[CustomMesh] Animation component state - playing:', component.playing, 'speed:', component.speed);\n }\n }\n catch (err) {\n console.error('[CustomMesh] Error in playAnimComponentV2:', err);\n }\n }\n /**\n * Enhanced animation pauser\n */\n function pauseAnimComponentV2(animInfo: AnimInfo): void {\n const { type, component } = animInfo;\n if (!component && type !== 'glb-skeleton') return;\n try {\n if (type === 'pc-anim' && component) {\n // Simple PlayCanvas anim component - matches HTML export approach\n component.playing = false;\n animInfo.isPlaying = false;\n console.log('[CustomMesh] pc-anim animation paused');\n }\n else if (type === 'glb-skeleton') {\n // Stop GLB skeleton animation\n animInfo.isPlaying = false;\n // If we have an AnimComponent, pause it\n if (animInfo.animComponent) {\n animInfo.animComponent.playing = false;\n console.log('[CustomMesh] GLB skeleton animation paused via AnimComponent');\n }\n // Remove update handler if using manual playback\n if (animInfo.updateHandler) {\n app.off('update', animInfo.updateHandler);\n animInfo.updateHandler = null;\n console.log('[CustomMesh] GLB manual animation paused');\n }\n }\n else if (component) {\n component.playing = false;\n component.speed = 0;\n if (type === 'animation' && typeof component.pause === 'function') {\n component.pause();\n }\n }\n }\n catch (err) {\n console.error('[CustomMesh] Error pausing animation:', err);\n }\n }\n /**\n * Load and create a custom mesh entity (GLB/GLTF model)\n * Matches HTML export customMeshSystem.ts\n */\n function loadCustomMesh(meshConfig: CustomMeshConfigLike, index: number): pc.Entity | null {\n console.log('[CustomMesh] Loading mesh', index, ':', meshConfig.name, meshConfig);\n // Validate modelUrl before attempting to load\n if (!meshConfig.modelUrl || typeof meshConfig.modelUrl !== 'string' || meshConfig.modelUrl.trim() === '') {\n console.warn('[CustomMesh] Skipping mesh', meshConfig.name, '- no valid modelUrl provided. Config:', meshConfig);\n return null;\n }\n // Create container entity\n const entity = new pc.Entity('custom-mesh-' + index);\n // Position (negate Z for BabylonJS -> PlayCanvas coordinate conversion)\n const pos = meshConfig.position || { x: 0, y: 0, z: 0 };\n entity.setPosition(pos._x ?? pos.x ?? 0, pos._y ?? pos.y ?? 0, -(pos._z ?? pos.z ?? 0));\n // Rotation (BJS YXZ Euler radians → PC quaternion)\n if (meshConfig.rotation) {\n const rot = meshConfig.rotation;\n entity.setRotation(bjsRotationToPC(rot._x ?? rot.x ?? 0, rot._y ?? rot.y ?? 0, rot._z ?? rot.z ?? 0));\n }\n // Scale\n if (meshConfig.scale) {\n const scale = meshConfig.scale;\n entity.setLocalScale(scale._x ?? scale.x ?? 1, scale._y ?? scale.y ?? 1, scale._z ?? scale.z ?? 1);\n }\n // Load GLB/GLTF model\n const modelUrl = meshConfig.modelUrl.trim();\n console.log('[CustomMesh] Loading model from URL:', modelUrl);\n const modelAsset = new pc.Asset('mesh-model-' + index, 'container', { url: modelUrl });\n app.assets.add(modelAsset);\n // Store mesh data reference\n const meshId = meshConfig.id || `mesh-${index}`;\n const meshData: CustomMeshData = {\n entity,\n config: meshConfig,\n isAnimPlaying: false,\n audioPlaying: false\n };\n customMeshEntities.set(meshId, meshData);\n modelAsset.ready((asset: pc.Asset) => {\n try {\n console.log('[CustomMesh] Model loaded:', meshConfig.name);\n // Validate asset resource before instantiation\n if (!asset || !asset.resource) {\n console.error('[CustomMesh] Invalid asset resource for mesh:', meshConfig.name);\n return;\n }\n // Instantiate the model\n const containerResource = asset.resource as ContainerResourceWithAnimations | undefined;\n const modelEntity = containerResource?.instantiateRenderEntity();\n if (!modelEntity) {\n console.error('[CustomMesh] Failed to instantiate render entity for mesh:', meshConfig.name);\n return;\n }\n entity.addChild(modelEntity);\n // Store reference to model entity for animation\n (entity as ViewerRuntimeEntity).modelEntity = modelEntity;\n // IMPORTANT: Recursively enable all children in the hierarchy\n // Some GLB/GLTF files have nodes disabled by default\n function enableAllChildren(node: pc.Entity): void {\n node.enabled = true;\n if (node.children) {\n node.children.forEach((child) => {\n if (child instanceof pc.Entity) {\n enableAllChildren(child);\n }\n });\n }\n }\n enableAllChildren(modelEntity);\n // Count children for debugging\n let childCount = 0;\n modelEntity.forEach(() => { childCount++; });\n console.log('[CustomMesh] Enabled all children for:', meshConfig.name, '- Total nodes:', childCount);\n // Tame GLB materials to prevent overblown/too-shiny appearance\n // PlayCanvas PBR can appear brighter than BabylonJS with the same lighting setup\n // Reduce specular contribution on all materials in the loaded model\n function tameGlbMaterials(node: pc.Entity): void {\n const r = node.render;\n if (r && r.meshInstances) {\n for (const mi of r.meshInstances) {\n const mat = mi.material as pc.StandardMaterial | undefined;\n if (mat && typeof mat.update === 'function') {\n // Reduce specular intensity to prevent shiny look\n if (mat.specular) {\n mat.specular.mulScalar(0.3);\n }\n // Increase roughness slightly for less mirror-like reflections\n if (mat.gloss !== undefined) {\n mat.gloss = Math.min(mat.gloss, 60);\n }\n mat.update();\n }\n }\n }\n if (node.children) {\n node.children.forEach((child) => {\n if (child instanceof pc.Entity) tameGlbMaterials(child);\n });\n }\n }\n tameGlbMaterials(modelEntity);\n // Apply static opacity if specified (not animated mode)\n if (meshConfig.opacityMode !== 'animated' && meshConfig.opacity !== undefined && meshConfig.opacity < 1) {\n // Apply opacity to all mesh instances\n applyOpacityToCustomMesh(entity, meshConfig.opacity);\n console.log('[CustomMesh] Applied static opacity:', meshConfig.opacity, 'to', meshConfig.name);\n }\n // Setup billboarding if enabled (with range support)\n if (meshConfig.billboard) {\n // Store original rotation for when billboard is disabled\n const originalRotation = entity.getEulerAngles().clone();\n // Initialize billboard active state\n (entity as ViewerRuntimeEntity)._billboardActive = !meshConfig.billboardRange; // Active by default if no range\n // Create update handler to make mesh face camera when billboard is active\n app.on('update', () => {\n if (!entity.enabled)\n return;\n // Check if billboard should be active (controlled by updateCustomMeshVisibility)\n const billboardActive = (entity as ViewerRuntimeEntity)._billboardActive;\n if (!billboardActive) {\n // Restore original rotation when billboard is disabled\n entity.setEulerAngles(originalRotation.x, originalRotation.y, originalRotation.z);\n return;\n }\n const camPos = camera.getPosition();\n const meshPos = entity.getPosition();\n // Calculate angle to camera (Y-axis rotation only for billboard)\n const dx = camPos.x - meshPos.x;\n const dz = camPos.z - meshPos.z;\n const angle = Math.atan2(dx, dz) * (180 / Math.PI);\n // Get current rotation and only update Y\n const currentRot = entity.getEulerAngles();\n entity.setEulerAngles(currentRot.x, angle, currentRot.z);\n });\n console.log('[CustomMesh] Billboard enabled for:', meshConfig.name, meshConfig.billboardRange ? '(with range control)' : '(always active)');\n }\n // Handle animations - ALWAYS check for GLB animations regardless of interaction settings\n // Log what's available in the asset for debugging\n const hasGlbAnimations = (containerResource?.animations?.length ?? 0) > 0;\n console.log('[CustomMesh] Asset resource for', meshConfig.name, ':', {\n hasAnimations: hasGlbAnimations,\n animationCount: containerResource?.animations?.length || 0,\n animations: containerResource?.animations?.map((a) => a?.name || a?.resource?.name || 'unnamed') || [],\n interactionConfig: meshConfig.interaction\n });\n // Setup animations if the model has them\n // APPROACH: Match HTML export - prioritize modelEntity.anim first, then fallback\n if (hasGlbAnimations || (meshConfig.interaction && meshConfig.interaction.playModelAnimation)) {\n const allAnimComponents: AnimInfo[] = [];\n // PRIORITY 1: Check if PlayCanvas auto-created an anim component (like HTML export expects)\n const animComponent = (modelEntity as AnimatedEntity).anim;\n if (animComponent) {\n console.log('[CustomMesh] Found modelEntity.anim component - using simple approach');\n allAnimComponents.push({\n type: 'pc-anim',\n component: animComponent,\n modelEntity: modelEntity as unknown as AnimatedEntity\n });\n }\n // PRIORITY 2: Search hierarchy for anim/animation components\n if (allAnimComponents.length === 0) {\n function findAllAnimComponents(node: AnimatedEntity, depth: number = 0): void {\n // Check for new Anim component\n if (node.anim && !allAnimComponents.find((a) => a.component === node.anim)) {\n allAnimComponents.push({ type: 'anim', component: node.anim, node: node as unknown as pc.Entity });\n console.log('[CustomMesh] Found existing ANIM component on:', node.name);\n }\n // Check for legacy Animation component\n if (node.animation) {\n allAnimComponents.push({ type: 'animation', component: node.animation, node: node as unknown as pc.Entity });\n console.log('[CustomMesh] Found ANIMATION (legacy) component on:', node.name);\n }\n if (node.children) {\n node.children.forEach((child) => {\n if (child instanceof pc.Entity) {\n findAllAnimComponents(child as AnimatedEntity, depth + 1);\n }\n });\n }\n }\n findAllAnimComponents(modelEntity as AnimatedEntity);\n }\n // PRIORITY 3: Fall back to manual GLB skeleton animation approach\n if (allAnimComponents.length === 0 && hasGlbAnimations) {\n const animations = containerResource?.animations || [];\n console.log('[CustomMesh] No anim component found - falling back to GLB skeleton approach');\n console.log('[CustomMesh] GLB has', animations.length, 'embedded animations');\n try {\n allAnimComponents.push({\n type: 'glb-skeleton',\n modelEntity: modelEntity as unknown as AnimatedEntity,\n asset: asset,\n animations: animations,\n animationNames: animations.map((a) => a.resource?.name || a.name || 'Animation'),\n isPlaying: false,\n currentTime: 0\n });\n console.log('[CustomMesh] GLB skeleton animations stored:', animations.map((a) => a.resource?.name || a.name));\n }\n catch (err) {\n console.error('[CustomMesh] Error setting up GLB animations:', err);\n }\n }\n if (allAnimComponents.length > 0) {\n // Store all animation components (now with type info)\n meshData.allAnimComponents = allAnimComponents;\n meshData.animComponent = allAnimComponents[0].component;\n console.log('[CustomMesh] Total animation components for', meshConfig.name, ':', allAnimComponents.length, '- Types:', allAnimComponents.map((a) => a.type).join(', '));\n // Auto-play if enabled OR if animationAutoPlay is set\n const shouldAutoPlay = meshConfig.interaction?.animationAutoPlay;\n if (shouldAutoPlay) {\n allAnimComponents.forEach((animInfo) => {\n playAnimComponentV2(animInfo);\n });\n meshData.isAnimPlaying = true;\n console.log('[CustomMesh] Auto-playing all animations for:', meshConfig.name);\n }\n }\n else {\n console.warn('[CustomMesh] No animation components could be set up for mesh:', meshConfig.name);\n }\n }\n else {\n console.log('[CustomMesh] No animations to setup for:', meshConfig.name, '(no GLB animations and playModelAnimation not enabled)');\n }\n // Setup spatial audio if configured\n if (meshConfig.interaction && meshConfig.interaction.playAudio && meshConfig.interaction.audioUrl) {\n setupMeshAudio(entity, meshConfig, meshData);\n }\n // Setup click interaction\n if (meshConfig.interaction) {\n setupMeshClickInteraction(entity, meshConfig, meshData);\n }\n console.log('[CustomMesh] Mesh fully initialized:', meshConfig.name);\n }\n catch (err) {\n console.error('[CustomMesh] Error processing loaded mesh:', meshConfig.name, err);\n }\n });\n modelAsset.on('error', (err: unknown) => {\n console.error('[CustomMesh] Failed to load mesh:', meshConfig.name, err);\n // Remove failed asset to prevent further issues\n app.assets.remove(modelAsset);\n });\n app.assets.load(modelAsset);\n // Visibility range setup\n if (meshConfig.visibilityRange) {\n (entity as ViewerRuntimeEntity).visibilityRange = meshConfig.visibilityRange;\n entity.enabled = meshConfig.enabled !== false;\n }\n else {\n entity.enabled = meshConfig.enabled !== false;\n }\n // Opacity configuration\n (entity as ViewerRuntimeEntity).opacityConfig = {\n mode: meshConfig.opacityMode || 'static',\n value: meshConfig.opacity !== undefined ? meshConfig.opacity : 1\n };\n app.root.addChild(entity);\n return entity;\n }\n /**\n * Setup spatial audio for mesh\n */\n function setupMeshAudio(entity: pc.Entity, meshConfig: CustomMeshConfigLike, meshData: CustomMeshData): void {\n const audioConfig = meshConfig.interaction;\n if (!audioConfig) return;\n const meshId = meshConfig.id || meshConfig.name;\n const slotId = `mesh-audio-${meshId}`;\n // Add sound component to entity\n entity.addComponent('sound', {\n positional: audioConfig.audioSpatial || false,\n distanceModel: audioConfig.audioDistanceModel || 'exponential',\n refDistance: audioConfig.audioRefDistance || 1,\n maxDistance: audioConfig.audioMaxDistance || 100,\n rollOffFactor: audioConfig.audioRolloffFactor || 1,\n slots: {\n [slotId]: {\n name: slotId,\n loop: audioConfig.audioLoop || false,\n autoPlay: false,\n volume: audioConfig.audioVolume !== undefined ? audioConfig.audioVolume : 1,\n pitch: 1\n }\n }\n });\n meshData.audioSlotId = slotId;\n // Load audio asset\n const audioAsset = new pc.Asset(`mesh-audio-asset-${meshId}`, 'audio', { url: audioConfig.audioUrl });\n app.assets.add(audioAsset);\n audioAsset.ready(() => {\n const slot = entity.sound?.slot(slotId);\n if (slot) {\n slot.asset = audioAsset.id;\n }\n console.log('[CustomMesh] Audio loaded for mesh:', meshConfig.name, 'Spatial:', audioConfig.audioSpatial);\n });\n audioAsset.on('error', (err: unknown) => {\n console.error('[CustomMesh] Failed to load mesh audio:', meshConfig.name, err);\n });\n app.assets.load(audioAsset);\n }\n /**\n * Check if a ray intersects any mesh render component in the entity hierarchy\n */\n function rayIntersectsMeshHierarchy(entity: pc.Entity, rayOrigin: pc.Vec3, rayDir: pc.Vec3): boolean {\n // Check this entity's render components\n const render = (entity as ViewerRuntimeEntity).render;\n if (render && render.meshInstances) {\n for (const mi of render.meshInstances) {\n if (mi.aabb) {\n const intersection = rayIntersectsAABB(rayOrigin, rayDir, mi.aabb);\n if (intersection)\n return true;\n }\n }\n }\n // Check model component if present\n const model = (entity as ViewerRuntimeEntity).model;\n if (model && model.meshInstances) {\n for (const mi of model.meshInstances) {\n if (mi.aabb) {\n const intersection = rayIntersectsAABB(rayOrigin, rayDir, mi.aabb);\n if (intersection)\n return true;\n }\n }\n }\n // Recursively check children\n const children = entity.children;\n if (children) {\n for (const child of children) {\n if (child instanceof pc.Entity && rayIntersectsMeshHierarchy(child, rayOrigin, rayDir)) {\n return true;\n }\n }\n }\n return false;\n }\n /**\n * Ray-AABB intersection test\n */\n function rayIntersectsAABB(rayOrigin: pc.Vec3, rayDir: pc.Vec3, aabb: {\n getMin?: () => LegacyVec3 & {\n data?: number[];\n };\n getMax?: () => LegacyVec3 & {\n data?: number[];\n };\n min?: LegacyVec3 & {\n data?: number[];\n };\n max?: LegacyVec3 & {\n data?: number[];\n };\n }): boolean {\n const min = aabb.getMin ? aabb.getMin() : aabb.min;\n const max = aabb.getMax ? aabb.getMax() : aabb.max;\n if (!min || !max)\n return false;\n let tmin = -Infinity;\n let tmax = Infinity;\n const axes = ['x', 'y', 'z'] as const;\n const readAxis = (value: LegacyVec3 & {\n data?: number[];\n }, axis: 'x' | 'y' | 'z', index: number): number => {\n const byAxis = value[axis];\n if (typeof byAxis === 'number')\n return byAxis;\n const byUnderscore = value[`_${axis}` as '_x' | '_y' | '_z'];\n if (typeof byUnderscore === 'number')\n return byUnderscore;\n return value.data?.[index] ?? 0;\n };\n for (let i = 0; i < 3; i++) {\n const axis = axes[i];\n const origin = rayOrigin[axis];\n const dir = rayDir[axis];\n const minVal = readAxis(min, axis, i);\n const maxVal = readAxis(max, axis, i);\n if (Math.abs(dir) < 1e-8) {\n if (origin < minVal || origin > maxVal)\n return false;\n }\n else {\n let t1 = (minVal - origin) / dir;\n let t2 = (maxVal - origin) / dir;\n if (t1 > t2)\n [t1, t2] = [t2, t1];\n tmin = Math.max(tmin, t1);\n tmax = Math.min(tmax, t2);\n if (tmin > tmax)\n return false;\n }\n }\n return tmax >= 0;\n }\n /**\n * Check if a custom mesh has any active interactions\n * Matches HTML export's hasInteractions function\n */\n function hasInteractions(customMesh: CustomMeshConfigLike): boolean {\n const interaction = customMesh.interaction;\n if (!interaction)\n return false;\n return !!(interaction.triggerUIPopup ||\n interaction.playAudio ||\n interaction.playModelAnimation ||\n interaction.triggerDirectLink);\n }\n /**\n * Display mesh content using the hotspot popup system\n * Matches HTML export's displayMeshContent function\n */\n function displayMeshContent(customMesh: CustomMeshConfigLike): void {\n if (!customMesh.interaction)\n return;\n // Create a hotspot-like object to use with the hotspot popup system\n const hotspotData = {\n id: `mesh-content-${customMesh.id}`,\n title: customMesh.interaction.title || customMesh.name,\n information: customMesh.interaction.information,\n photoUrl: customMesh.interaction.photoUrl,\n iframeUrl: customMesh.interaction.iframeUrl,\n externalLinkUrl: customMesh.interaction.externalLinkUrl,\n externalLinkText: customMesh.interaction.externalLinkText,\n // Style options\n backgroundColor: customMesh.interaction.backgroundColor || '#000000',\n textColor: customMesh.interaction.textColor || '#ffffff',\n };\n // Use the viewer UI's hotspot popup if available, otherwise use our own popup\n if ((uiElements as UIElementsWithPortalPopup).showHotspotPopup) {\n (uiElements as UIElementsWithPortalPopup).showHotspotPopup!(hotspotData as HotspotData);\n }\n else {\n showMeshPopup(customMesh);\n }\n }\n /**\n * Hide mesh content popup\n */\n function hideMeshContent(): void {\n // Remove hotspot popup if visible\n const hotspotPopup = document.querySelector('.storysplat-hotspot-popup');\n if (hotspotPopup)\n hotspotPopup.remove();\n // Remove mesh popup if visible\n const meshPopup = document.querySelector('.storysplat-mesh-popup');\n if (meshPopup)\n meshPopup.remove();\n }\n /**\n * Handle direct link trigger for a mesh\n * Matches HTML export's handleDirectLink function\n */\n function handleDirectLink(customMesh: CustomMeshConfigLike): void {\n if (!customMesh.interaction?.triggerDirectLink || !customMesh.interaction.directLinkUrl)\n return;\n window.open(customMesh.interaction.directLinkUrl, '_blank');\n }\n /**\n * Setup hover and click interactions for mesh\n * Enhanced to match HTML export's setupMeshInteractions with per-interaction trigger modes\n */\n function setupMeshClickInteraction(entity: pc.Entity, meshConfig: CustomMeshConfigLike, meshData: CustomMeshData): void {\n // Skip if no active interactions\n if (!hasInteractions(meshConfig)) {\n console.log('[CustomMesh] Skipping interaction setup - no active interactions for:', meshConfig.name);\n return;\n }\n // Get the global activation mode (fallback for per-interaction modes)\n const activationMode = meshConfig.interaction?.activationMode || 'click';\n // Click handler using canvas click + raycast\n const canvasForMesh = app.graphicsDevice.canvas as HTMLCanvasElement;\n // Helper to perform raycast\n const performRaycast = (clientX: number, clientY: number): boolean => {\n const rect = canvasForMesh.getBoundingClientRect();\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n // Raycast from camera through click point\n const from = camera.camera!.screenToWorld(x, y, camera.camera!.nearClip);\n const to = camera.camera!.screenToWorld(x, y, camera.camera!.farClip);\n const dir = new pc.Vec3().sub2(to, from).normalize();\n // Get the model entity (child of container entity)\n const modelEntity = (entity as ViewerRuntimeEntity).modelEntity as pc.Entity | undefined;\n // Check intersection with model entity hierarchy (includes all children)\n if (modelEntity && rayIntersectsMeshHierarchy(modelEntity, from, dir)) {\n return true;\n }\n // Fallback: also check the container entity itself\n if (rayIntersectsMeshHierarchy(entity, from, dir)) {\n return true;\n }\n // Final fallback: distance-based check for small or simple meshes\n const meshPos = entity.getPosition();\n const toMesh = new pc.Vec3().sub2(meshPos, from);\n const t = toMesh.dot(dir);\n if (t > 0) {\n const closestPoint = new pc.Vec3().add2(from, dir.clone().mulScalar(t));\n const distance = closestPoint.distance(meshPos);\n const scale = entity.getLocalScale();\n const hitRadius = Math.max(scale.x, scale.y, scale.z) * 1.5; // Larger radius for fallback\n if (distance < hitRadius) {\n return true;\n }\n }\n return false;\n };\n // Get per-interaction trigger modes (matches HTML export pattern)\n const popupTriggerMode = meshConfig.interaction?.popupTriggerMode || activationMode;\n const audioTriggerMode = meshConfig.interaction?.audioTriggerMode || activationMode;\n const animationTriggerMode = meshConfig.interaction?.animationTriggerMode || activationMode;\n const directLinkTriggerMode = meshConfig.interaction?.directLinkTriggerMode || activationMode;\n // Track hover state for this mesh\n let isHovering = false;\n /**\n * Handle HOVER IN interactions\n * Matches HTML export's OnPointerOverTrigger behavior\n */\n const handleHoverIn = () => {\n // Change cursor to pointer\n canvasForMesh.style.cursor = 'pointer';\n // UI Popup - trigger on hover if popupTriggerMode is 'hover'\n if (popupTriggerMode === 'hover' && meshConfig.interaction?.triggerUIPopup) {\n displayMeshContent(meshConfig);\n }\n // Audio - trigger on hover if audioTriggerMode is 'hover'\n if (audioTriggerMode === 'hover' && meshConfig.interaction?.playAudio && meshData.audioSlotId) {\n const slot = entity.sound?.slot(meshData.audioSlotId);\n if (slot && !slot.isPlaying) {\n slot.play();\n meshData.audioPlaying = true;\n console.log('[CustomMesh] Playing audio on hover for:', meshConfig.name);\n }\n }\n // Animation - trigger on hover if animationTriggerMode is 'hover'\n if (animationTriggerMode === 'hover' && meshConfig.interaction?.playModelAnimation) {\n const allAnimComponents = meshData.allAnimComponents;\n if (allAnimComponents && allAnimComponents.length > 0 && !meshData.isAnimPlaying) {\n allAnimComponents.forEach((animInfo) => playAnimComponentV2(animInfo));\n meshData.isAnimPlaying = true;\n console.log('[CustomMesh] Playing animation on hover for:', meshConfig.name);\n }\n }\n // Direct link - trigger on hover if directLinkTriggerMode is 'hover'\n if (directLinkTriggerMode === 'hover' && meshConfig.interaction?.triggerDirectLink) {\n handleDirectLink(meshConfig);\n }\n };\n /**\n * Handle HOVER OUT interactions\n * Matches HTML export's OnPointerOutTrigger behavior\n */\n const handleHoverOut = () => {\n // Reset cursor\n canvasForMesh.style.cursor = '';\n // UI Popup - hide on hover out if popupTriggerMode is 'hover'\n if (popupTriggerMode === 'hover' && meshConfig.interaction?.triggerUIPopup) {\n hideMeshContent();\n }\n // Audio - stop on hover out if audioTriggerMode is 'hover'\n if (audioTriggerMode === 'hover' && meshConfig.interaction?.playAudio && meshData.audioSlotId) {\n const slot = entity.sound?.slot(meshData.audioSlotId);\n if (slot && slot.isPlaying) {\n slot.stop();\n meshData.audioPlaying = false;\n console.log('[CustomMesh] Stopped audio on hover out for:', meshConfig.name);\n }\n }\n // Animation - pause on hover out if animationTriggerMode is 'hover'\n if (animationTriggerMode === 'hover' && meshConfig.interaction?.playModelAnimation) {\n const allAnimComponents = meshData.allAnimComponents;\n if (allAnimComponents && allAnimComponents.length > 0 && meshData.isAnimPlaying) {\n allAnimComponents.forEach((animInfo) => pauseAnimComponentV2(animInfo));\n meshData.isAnimPlaying = false;\n console.log('[CustomMesh] Paused animation on hover out for:', meshConfig.name);\n }\n }\n };\n /**\n * Handle CLICK interactions\n * Matches HTML export's OnPickTrigger behavior with toggle support\n */\n const handleClick = () => {\n console.log('[CustomMesh] Clicked mesh:', meshConfig.name);\n // UI Popup - trigger on click if popupTriggerMode is 'click'\n if (popupTriggerMode === 'click' && meshConfig.interaction?.triggerUIPopup) {\n displayMeshContent(meshConfig);\n }\n // Animation - toggle on click if animationTriggerMode is 'click'\n if (animationTriggerMode === 'click' && meshConfig.interaction?.playModelAnimation) {\n const allAnimComponents = meshData.allAnimComponents;\n if (allAnimComponents && allAnimComponents.length > 0) {\n if (meshData.isAnimPlaying) {\n allAnimComponents.forEach((animInfo) => pauseAnimComponentV2(animInfo));\n meshData.isAnimPlaying = false;\n console.log('[CustomMesh] Paused', allAnimComponents.length, 'animations on click for:', meshConfig.name);\n }\n else {\n allAnimComponents.forEach((animInfo) => playAnimComponentV2(animInfo));\n meshData.isAnimPlaying = true;\n console.log('[CustomMesh] Playing', allAnimComponents.length, 'animations on click for:', meshConfig.name);\n }\n }\n }\n // Audio - toggle on click if audioTriggerMode is 'click'\n if (audioTriggerMode === 'click' && meshConfig.interaction?.playAudio && meshData.audioSlotId) {\n const slot = entity.sound?.slot(meshData.audioSlotId);\n if (slot) {\n if (slot.isPlaying) {\n slot.pause();\n meshData.audioPlaying = false;\n console.log('[CustomMesh] Paused audio on click for:', meshConfig.name);\n }\n else {\n slot.play();\n meshData.audioPlaying = true;\n console.log('[CustomMesh] Playing audio on click for:', meshConfig.name);\n }\n }\n }\n // Direct link - trigger on click if directLinkTriggerMode is 'click'\n if (directLinkTriggerMode === 'click' && meshConfig.interaction?.triggerDirectLink) {\n handleDirectLink(meshConfig);\n }\n };\n // Click handler\n const meshClickHandler = (e: MouseEvent) => {\n if (performRaycast(e.clientX, e.clientY)) {\n handleClick();\n }\n };\n // Hover handler - handles both hover in and hover out\n const meshHoverHandler = (e: MouseEvent) => {\n const nowHovering = performRaycast(e.clientX, e.clientY);\n if (nowHovering && !isHovering) {\n isHovering = true;\n handleHoverIn();\n }\n else if (!nowHovering && isHovering) {\n isHovering = false;\n handleHoverOut();\n }\n };\n // Mouse leave handler to reset hover state\n const meshLeaveHandler = () => {\n if (isHovering) {\n isHovering = false;\n handleHoverOut();\n }\n };\n canvasForMesh.addEventListener('click', meshClickHandler);\n canvasForMesh.addEventListener('mousemove', meshHoverHandler);\n canvasForMesh.addEventListener('mouseleave', meshLeaveHandler);\n // Store handlers for cleanup\n (entity as ViewerRuntimeEntity).meshClickHandler = meshClickHandler;\n (entity as ViewerRuntimeEntity).meshHoverHandler = meshHoverHandler;\n (entity as ViewerRuntimeEntity).meshLeaveHandler = meshLeaveHandler;\n }\n /**\n * Show mesh popup\n */\n function showMeshPopup(meshConfig: CustomMeshConfigLike): void {\n // Remove existing popup if any\n const existingPopup = document.querySelector('.storysplat-mesh-popup');\n if (existingPopup)\n existingPopup.remove();\n const popup = document.createElement('div');\n popup.className = 'storysplat-mesh-popup';\n popup.style.cssText = `\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: rgba(0, 0, 0, 0.9);\n padding: 30px;\n border-radius: 12px;\n max-width: 600px;\n max-height: 80vh;\n overflow: auto;\n z-index: 100001;\n color: white;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n `;\n const title = document.createElement('h2');\n title.textContent = meshConfig.name || 'Custom Mesh';\n title.style.cssText = 'margin-top: 0; margin-bottom: 16px;';\n popup.appendChild(title);\n if (meshConfig.interaction?.popupContent) {\n const content = document.createElement('p');\n content.textContent = meshConfig.interaction.popupContent;\n content.style.cssText = 'margin: 0; line-height: 1.6;';\n popup.appendChild(content);\n }\n const closeBtn = document.createElement('button');\n closeBtn.textContent = `\\u00d7 ${getButtonLabel(currentButtonLabels, 'close')}`;\n closeBtn.style.cssText = `\n position: absolute;\n top: 10px;\n right: 10px;\n background: rgba(255, 255, 255, 0.2);\n border: none;\n color: white;\n padding: 8px 16px;\n border-radius: 6px;\n cursor: pointer;\n font-size: 16px;\n `;\n closeBtn.onclick = () => popup.remove();\n popup.appendChild(closeBtn);\n document.body.appendChild(popup);\n }\n /**\n * Update mesh visibility based on current waypoint/progress\n * Called when waypoint changes or progress updates\n */\n function updateCustomMeshVisibility(): void {\n const scrollPercent = currentProgress * 100;\n const numWaypoints = config.waypoints?.length || 1;\n const waypointIndex = Math.round(currentProgress * Math.max(1, numWaypoints - 1));\n customMeshEntities.forEach((meshData) => {\n const { entity, config: meshConfig } = meshData;\n const range = (entity as ViewerRuntimeEntity).visibilityRange;\n if (range) {\n let visible = true;\n if (range.type === 'waypoint') {\n // Show only between specific waypoints\n visible = waypointIndex >= range.start && waypointIndex <= range.end;\n }\n else if (range.type === 'percentage') {\n // Show based on scroll percentage (0-100)\n visible = scrollPercent >= range.start && scrollPercent <= range.end;\n }\n entity.enabled = visible;\n }\n // Update opacity if animated\n if (meshConfig.opacityMode === 'animated' && meshConfig.opacityAnimation) {\n const anim = meshConfig.opacityAnimation;\n if (scrollPercent >= anim.startPercent && scrollPercent <= anim.endPercent) {\n const progress = (scrollPercent - anim.startPercent) / (anim.endPercent - anim.startPercent);\n const opacity = anim.startOpacity + (anim.endOpacity - anim.startOpacity) * progress;\n // Apply opacity to all mesh instances in the entity\n applyOpacityToCustomMesh(entity, opacity);\n }\n }\n // Update billboard state based on range (matches HTML export lines 664-674)\n if (meshConfig.billboard && meshConfig.billboardRange) {\n const bRange = meshConfig.billboardRange;\n let billboardActive = false;\n if (bRange.type === 'percentage') {\n billboardActive = scrollPercent >= bRange.start && scrollPercent <= bRange.end;\n }\n else if (bRange.type === 'waypoint') {\n billboardActive = waypointIndex >= bRange.start && waypointIndex <= bRange.end;\n }\n // Store billboard state for the update loop to use\n (entity as ViewerRuntimeEntity)._billboardActive = billboardActive;\n }\n else if (meshConfig.billboard) {\n // Billboard always active if no range specified\n (entity as ViewerRuntimeEntity)._billboardActive = true;\n }\n });\n }\n /**\n * Apply opacity to all mesh render components\n * Uses BLEND_PREMULTIPLIED with depthWrite=true for proper GSplat depth sorting\n */\n function applyOpacityToCustomMesh(entity: pc.Entity, opacity: number): void {\n function applyToEntity(ent: pc.Entity): void {\n if (ent.render && ent.render.meshInstances) {\n ent.render.meshInstances.forEach((meshInstance) => {\n if (meshInstance.material) {\n // Clone material if it's shared to avoid affecting other instances\n const stdMat = meshInstance.material as pc.StandardMaterial & { _isCloned?: boolean };\n if (!stdMat._isCloned) {\n const cloned = meshInstance.material.clone() as pc.StandardMaterial & { _isCloned?: boolean };\n cloned._isCloned = true;\n meshInstance.material = cloned;\n }\n (meshInstance.material as pc.StandardMaterial).opacity = opacity;\n // Use premultiplied alpha with depth write for proper GSplat depth sorting\n meshInstance.material.blendType = pc.BLEND_PREMULTIPLIED;\n meshInstance.material.depthTest = true;\n meshInstance.material.depthWrite = true;\n meshInstance.material.alphaTest = 0.01;\n meshInstance.material.update();\n }\n });\n }\n // Apply to children\n ent.children.forEach((child) => {\n if (child instanceof pc.Entity) {\n applyToEntity(child);\n }\n });\n }\n applyToEntity(entity);\n }\n /**\n * Initialize all custom meshes from config\n */\n async function initCustomMeshes(): Promise<void> {\n if (!config.customMeshes || config.customMeshes.length === 0) {\n console.log('[StorySplat Viewer] No custom meshes to create');\n return;\n }\n console.log(`[StorySplat Viewer] Creating ${config.customMeshes.length} custom meshes...`);\n console.log('[StorySplat Viewer] All custom mesh configs:', config.customMeshes);\n // Defer mesh loading to ensure PlayCanvas is fully initialized\n // This helps avoid race conditions with the asset loader\n setTimeout(() => {\n let loadedCount = 0;\n let skippedCount = 0;\n config.customMeshes!.forEach((meshConfig, index: number) => {\n console.log(`[CustomMesh] Processing mesh ${index}:`, {\n name: meshConfig.name,\n enabled: meshConfig.enabled,\n hasModelUrl: !!meshConfig.modelUrl,\n modelUrl: meshConfig.modelUrl?.substring(0, 100) + '...'\n });\n if (meshConfig.enabled !== false) {\n try {\n const entity = loadCustomMesh(meshConfig, index);\n if (entity) {\n loadedCount++;\n }\n else {\n skippedCount++;\n console.warn(`[CustomMesh] Mesh ${meshConfig.name} returned null (likely missing modelUrl)`);\n }\n }\n catch (err) {\n console.error('[CustomMesh] Error loading mesh', meshConfig.name, ':', err);\n skippedCount++;\n }\n }\n else {\n console.log('[CustomMesh] Skipping disabled mesh:', meshConfig.name, '(enabled =', meshConfig.enabled, ')');\n skippedCount++;\n }\n });\n console.log(`[CustomMesh] Summary: ${loadedCount} loaded, ${skippedCount} skipped`);\n }, 100);\n console.log(`[StorySplat Viewer] ${config.customMeshes.length} custom meshes queued for loading`);\n }\n /**\n * Cleanup all custom meshes\n */\n function cleanupCustomMeshes(): void {\n const canvasForCleanup = app.graphicsDevice.canvas as HTMLCanvasElement;\n customMeshEntities.forEach((meshData) => {\n const entity = meshData.entity;\n const clickHandler = (entity as ViewerRuntimeEntity).meshClickHandler;\n if (clickHandler) {\n canvasForCleanup.removeEventListener('click', clickHandler);\n }\n entity.destroy();\n });\n customMeshEntities.clear();\n }\n // Listen for progress updates to update mesh visibility\n events.on('progressUpdate', () => {\n updateCustomMeshVisibility();\n });\n // =====================================================\n // SKYBOX SYSTEM\n // =====================================================\n let currentSkyboxEntity: pc.Entity | null = null;\n let skyboxFollowCameraHandler: ((dt: number) => void) | null = null;\n /**\n * Initialize skybox from config with IBL (Image-Based Lighting) support\n * Matches HTML export skyboxSystem.ts\n * Supports both nested (config.skybox.url) and flat (config.skyboxUrl) formats\n */\n function initSkybox(): void {\n // Support both config formats: nested object and flat properties\n const skyboxUrl = config.skybox?.url || config.skyboxUrl;\n if (!skyboxUrl) {\n console.log('[StorySplat Viewer] No skybox configured');\n return;\n }\n // Get rotation from either format (value is in radians)\n const skyboxRotation = config.skybox?.rotation ?? config.skyboxRotation ?? 0;\n const skyboxIntensity = config.skybox?.intensity ?? 1.0;\n const enableIBL = config.skybox?.enableIBL ?? true;\n console.log('[StorySplat Viewer] Creating skybox:', skyboxUrl, 'rotation:', skyboxRotation, 'rad =', skyboxRotation * (180 / Math.PI), 'deg', 'IBL:', enableIBL);\n // Check if URL is an HDR/EXR environment map or a regular image\n const isHDR = skyboxUrl.toLowerCase().includes('.hdr') || skyboxUrl.toLowerCase().includes('.exr');\n // Create a large sphere for the skybox (inverted normals) - visual display\n // Remove existing skybox if replacing\n if (currentSkyboxEntity) {\n currentSkyboxEntity.destroy();\n currentSkyboxEntity = null;\n }\n if (skyboxFollowCameraHandler) {\n app.off('update', skyboxFollowCameraHandler);\n skyboxFollowCameraHandler = null;\n }\n const skyboxEntity = new pc.Entity('skybox');\n // Create skybox material\n const skyboxMaterial = new pc.StandardMaterial();\n skyboxMaterial.useLighting = false;\n // We render the viewer from inside the dome. If we simply render back faces,\n // the panorama appears mirrored. Instead, we mirror the mesh (negative X scale)\n // and render front faces, matching BabylonJS PhotoDome behavior.\n skyboxMaterial.cull = pc.CULLFACE_BACK;\n skyboxMaterial.depthTest = false;\n skyboxMaterial.depthWrite = false;\n // Load skybox texture\n const textureAsset = new pc.Asset(`skybox-texture-${Date.now()}`, 'texture', { url: skyboxUrl }, isHDR ? {\n // HDR/EXR equirectangular maps need RGBM decoding in PlayCanvas.\n type: pc.TEXTURETYPE_RGBM,\n mipmaps: true\n } : {\n mipmaps: true\n });\n app.assets.add(textureAsset);\n textureAsset.ready((asset: pc.Asset) => {\n const texture = asset.resource as pc.Texture;\n // Try to improve sampling quality for big panoramas.\n // (These properties exist on pc.Texture; if the runtime build differs, ignore.)\n try {\n texture.minFilter = pc.FILTER_LINEAR_MIPMAP_LINEAR;\n texture.magFilter = pc.FILTER_LINEAR;\n }\n catch {\n // ignore\n }\n // Apply to skybox sphere material\n skyboxMaterial.emissiveMap = texture;\n skyboxMaterial.emissive = new pc.Color(skyboxIntensity, skyboxIntensity, skyboxIntensity);\n skyboxMaterial.update();\n console.log('[StorySplat Viewer] Skybox texture loaded');\n // Apply IBL if enabled - use the texture for environment lighting\n if (enableIBL) {\n try {\n // Set scene exposure for HDR content\n if (isHDR) {\n app.scene.exposure = skyboxIntensity;\n // Set tone mapping via rendering options (if available)\n (app.scene as SceneWithToneMapping).toneMapping = pc.TONEMAP_ACES;\n console.log('[StorySplat Viewer] HDR tone mapping enabled');\n }\n // For IBL, we need to create environment lighting from the texture\n // PlayCanvas uses envAtlas for prefiltered environment maps\n // For a regular 2D texture, we apply ambient light color derived from it\n if (texture) {\n // Set ambient lighting based on skybox\n // Use a neutral ambient derived from skybox intensity\n app.scene.ambientLight = new pc.Color(0.3 * skyboxIntensity, 0.3 * skyboxIntensity, 0.35 * skyboxIntensity);\n // If we have a cubemap (6-face or equirectangular HDR), set it as env atlas\n // For now, enhance ambient based on the skybox\n console.log('[StorySplat Viewer] IBL ambient lighting applied with intensity:', skyboxIntensity);\n }\n }\n catch (iblError) {\n console.warn('[StorySplat Viewer] Failed to apply IBL:', iblError);\n }\n }\n });\n textureAsset.on('error', (err: unknown) => {\n console.error('[StorySplat Viewer] Failed to load skybox texture:', err);\n });\n app.assets.load(textureAsset);\n // Add sphere component\n skyboxEntity.addComponent('render', {\n type: 'sphere',\n material: skyboxMaterial,\n castShadows: false,\n receiveShadows: false\n });\n // Render on the default World layer with a very low drawOrder so the skybox\n // draws first. Combined with depthTest:false + depthWrite:false, this guarantees\n // the skybox fills the background and all scene objects render on top.\n // (LAYERID_SKYBOX is reserved for PlayCanvas's built-in cubemap skybox and does\n // not render custom mesh entities.)\n if (skyboxEntity.render && skyboxEntity.render.meshInstances.length > 0) {\n skyboxEntity.render.meshInstances[0].drawOrder = -10000;\n }\n // Scale to encompass scene (large sphere)\n // Negative X scale mirrors the dome so the panorama is not mirrored when viewed from inside.\n skyboxEntity.setLocalScale(-500, 500, 500);\n // Apply rotation\n if (skyboxRotation !== 0) {\n const rotationDegrees = skyboxRotation * (180 / Math.PI);\n skyboxEntity.setEulerAngles(0, rotationDegrees, 0);\n }\n // Make skybox follow camera position (so it always appears infinite)\n skyboxFollowCameraHandler = () => {\n const camPos = camera.getPosition();\n skyboxEntity.setPosition(camPos.x, camPos.y, camPos.z);\n };\n app.on('update', skyboxFollowCameraHandler);\n app.root.addChild(skyboxEntity);\n currentSkyboxEntity = skyboxEntity;\n console.log('[StorySplat Viewer] Skybox created with rotation:', skyboxRotation, 'intensity:', skyboxIntensity);\n }\n /**\n * Replace or remove the skybox at runtime (editor API).\n */\n function setSkyboxRuntime(url: string | null, rotation?: number): void {\n if (currentSkyboxEntity) {\n currentSkyboxEntity.destroy();\n currentSkyboxEntity = null;\n }\n if (skyboxFollowCameraHandler) {\n app.off('update', skyboxFollowCameraHandler);\n skyboxFollowCameraHandler = null;\n }\n if (url) {\n // Update config so initSkybox reads the new values\n config.skybox = { url, rotation: rotation ?? 0 };\n config.skyboxUrl = url;\n config.skyboxRotation = rotation ?? 0;\n initSkybox();\n }\n }\n // =====================================================\n // CUSTOM LIGHTING SYSTEM\n // =====================================================\n const lightEntities: pc.Entity[] = [];\n /**\n * Convert hex color to PlayCanvas Color\n */\n function hexToColorForLights(hex: string): pc.Color {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n if (result) {\n return new pc.Color(parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255);\n }\n return new pc.Color(1, 1, 1); // Default white\n }\n /**\n * Create point light\n */\n function createPointLight(lightConfig: LightConfigLike): pc.Entity {\n const entity = new pc.Entity(lightConfig.name || 'Point Light');\n entity.addComponent('light', {\n type: 'point',\n color: hexToColorForLights(lightConfig.color || '#ffffff'),\n intensity: lightConfig.intensity || 1,\n range: lightConfig.range || 10,\n castShadows: lightConfig.castShadows || false\n });\n // Position (negate Z for coordinate conversion)\n const pos = lightConfig.position;\n if (pos) {\n entity.setPosition(pos._x ?? pos.x ?? 0, pos._y ?? pos.y ?? 0, -(pos._z ?? pos.z ?? 0));\n }\n entity.enabled = lightConfig.enabled !== false;\n return entity;\n }\n /**\n * Create directional light\n */\n function createDirectionalLightEntity(lightConfig: LightConfigLike): pc.Entity {\n const entity = new pc.Entity(lightConfig.name || 'Directional Light');\n entity.addComponent('light', {\n type: 'directional',\n color: hexToColorForLights(lightConfig.color || '#ffffff'),\n intensity: lightConfig.intensity || 1,\n castShadows: lightConfig.castShadows || false\n });\n // Position (negate Z for coordinate conversion)\n const pos = lightConfig.position;\n if (pos) {\n entity.setPosition(pos._x ?? pos.x ?? 0, pos._y ?? pos.y ?? 0, -(pos._z ?? pos.z ?? 0));\n }\n // Direction (rotation - BJS YXZ Euler radians → PC quaternion)\n const rot = lightConfig.rotation;\n if (rot) {\n entity.setRotation(bjsRotationToPC(rot._x ?? rot.x ?? 0, rot._y ?? rot.y ?? 0, rot._z ?? rot.z ?? 0));\n }\n else {\n entity.setEulerAngles(45, 0, 0);\n }\n entity.enabled = lightConfig.enabled !== false;\n return entity;\n }\n /**\n * Create hemispheric light (approximated)\n */\n function createHemisphericLight(lightConfig: LightConfigLike): pc.Entity {\n const entity = new pc.Entity(lightConfig.name || 'Hemispheric Light');\n entity.addComponent('light', {\n type: 'directional',\n color: hexToColorForLights(lightConfig.color || '#ffffff'),\n intensity: lightConfig.intensity || 1,\n castShadows: false\n });\n // Position\n const pos = lightConfig.position;\n if (pos) {\n entity.setPosition(pos._x ?? pos.x ?? 0, pos._y ?? pos.y ?? 0, -(pos._z ?? pos.z ?? 0));\n }\n // Point upwards for hemispheric effect\n entity.setEulerAngles(-90, 0, 0);\n entity.enabled = lightConfig.enabled !== false;\n // If ground color is specified, adjust scene ambient\n if (lightConfig.groundColor) {\n const groundColor = hexToColorForLights(lightConfig.groundColor);\n const skyColor = hexToColorForLights(lightConfig.color || '#ffffff');\n app.scene.ambientLight = new pc.Color((skyColor.r + groundColor.r * 0.3) / 1.3, (skyColor.g + groundColor.g * 0.3) / 1.3, (skyColor.b + groundColor.b * 0.3) / 1.3);\n }\n return entity;\n }\n /**\n * Create ambient light\n */\n function createAmbientLight(lightConfig: LightConfigLike): null {\n const color = hexToColorForLights(lightConfig.color || '#404040');\n const intensity = lightConfig.intensity || 0.4;\n app.scene.ambientLight = new pc.Color(color.r * intensity, color.g * intensity, color.b * intensity);\n console.log('[StorySplat Viewer] Set ambient light:', lightConfig.name || 'Ambient Light');\n return null;\n }\n /**\n * Create spot light\n */\n function createSpotLight(lightConfig: LightConfigLike): pc.Entity {\n const entity = new pc.Entity(lightConfig.name || 'Spot Light');\n // Convert angles from radians to degrees if they come from the editor (stored in radians)\n // PlayCanvas expects degrees for cone angles\n const radToDeg = 180 / Math.PI;\n const angleRad = lightConfig.angle ?? (45 * Math.PI / 180); // Default to 45 degrees in radians\n const angleDeg = angleRad * radToDeg;\n // Inner cone angle is typically smaller than outer - use exponent to derive it\n const exponent = lightConfig.exponent ?? 2;\n const innerAngleDeg = lightConfig.innerConeAngle ?? lightConfig.innerAngle ?? (angleDeg * 0.8);\n const outerAngleDeg = lightConfig.outerConeAngle ?? lightConfig.outerAngle ?? angleDeg;\n entity.addComponent('light', {\n type: 'spot',\n color: hexToColorForLights(lightConfig.color || '#ffffff'),\n intensity: lightConfig.intensity || 1,\n range: lightConfig.range || 10,\n innerConeAngle: innerAngleDeg,\n outerConeAngle: outerAngleDeg,\n castShadows: lightConfig.castShadows || false,\n shadowBias: lightConfig.shadowBias ?? 0.05,\n normalOffsetBias: lightConfig.normalOffsetBias ?? 0.05\n });\n // Position (negate Z for coordinate conversion)\n const pos = lightConfig.position;\n if (pos) {\n entity.setPosition(pos._x ?? pos.x ?? 0, pos._y ?? pos.y ?? 0, -(pos._z ?? pos.z ?? 0));\n }\n // Direction (rotation - BJS YXZ Euler radians → PC quaternion)\n const rot = lightConfig.rotation;\n if (rot) {\n entity.setRotation(bjsRotationToPC(rot._x ?? rot.x ?? 0, rot._y ?? rot.y ?? 0, rot._z ?? rot.z ?? 0));\n }\n else if (lightConfig.direction) {\n // Alternative: direction vector to rotation\n const dir = lightConfig.direction;\n const dirVec = new pc.Vec3(dir._x ?? dir.x ?? 0, dir._y ?? dir.y ?? -1, -(dir._z ?? dir.z ?? 0)).normalize();\n // Calculate rotation from direction\n entity.lookAt(entity.getPosition().x + dirVec.x, entity.getPosition().y + dirVec.y, entity.getPosition().z + dirVec.z);\n }\n else {\n // Default: point downward\n entity.setEulerAngles(90, 0, 0);\n }\n entity.enabled = lightConfig.enabled !== false;\n return entity;\n }\n /**\n * Initialize all custom lights from config\n */\n function initCustomLights(): void {\n if (!config.lights || config.lights.length === 0) {\n console.log('[StorySplat Viewer] No custom lights to create');\n return;\n }\n console.log(`[StorySplat Viewer] Creating ${config.lights.length} custom lights...`);\n config.lights.forEach((lightConfig, index: number) => {\n let entity: pc.Entity | null = null;\n switch (lightConfig.type) {\n case 'point':\n entity = createPointLight(lightConfig);\n break;\n case 'directional':\n entity = createDirectionalLightEntity(lightConfig);\n break;\n case 'hemispheric':\n entity = createHemisphericLight(lightConfig);\n break;\n case 'ambient':\n createAmbientLight(lightConfig);\n break;\n case 'spot':\n entity = createSpotLight(lightConfig);\n break;\n default:\n console.warn('[StorySplat Viewer] Unknown light type:', lightConfig.type);\n return;\n }\n if (entity) {\n app.root.addChild(entity);\n lightEntities.push(entity);\n console.log(`[StorySplat Viewer] Created ${lightConfig.type} light:`, lightConfig.name || `Light ${index}`);\n }\n });\n console.log('[StorySplat Viewer] Lighting setup complete');\n }\n /**\n * Setup spatial audio for a hotspot using Web Audio API\n * Matches the HTML export's setupHotspotAudio function in hotspotSystem.ts\n */\n function setupHotspotAudio(entity: pc.Entity, hotspot: HotspotConfigLike): HotspotAudioElements | null {\n if (!hotspot.audioUrl)\n return null;\n const audio = document.createElement('audio');\n audio.src = hotspot.audioUrl;\n audio.loop = hotspot.audioLoop || false;\n audio.volume = hotspot.audioVolume !== undefined ? hotspot.audioVolume : 1;\n audio.crossOrigin = 'anonymous';\n allAudioElements.push(audio);\n if (hotspot.audioSpatial) {\n // Create AudioContext for spatial audio\n const audioCtx = new (window.AudioContext || (window as WindowWithLegacyAudio).webkitAudioContext)();\n allAudioContexts.push(audioCtx);\n const source = audioCtx.createMediaElementSource(audio);\n const panner = audioCtx.createPanner();\n // Apply spatial settings (matching HTML export)\n panner.panningModel = 'HRTF';\n panner.distanceModel = (hotspot.audioDistanceModel || 'linear') as DistanceModelType; // Match HTML export default\n panner.refDistance = hotspot.audioRefDistance !== undefined ? hotspot.audioRefDistance : 1;\n panner.maxDistance = hotspot.audioMaxDistance !== undefined ? hotspot.audioMaxDistance : 100;\n panner.rolloffFactor = hotspot.audioRolloffFactor !== undefined ? hotspot.audioRolloffFactor : 1;\n // Position audio at hotspot location\n const pos = entity.getPosition();\n panner.setPosition(pos.x, pos.y, pos.z);\n // Connect audio graph\n source.connect(panner);\n panner.connect(audioCtx.destination);\n // Update audio position each frame\n const updateAudioPosition = () => {\n if (!entity || !entity.getPosition)\n return;\n const hotspotPos = entity.getPosition();\n panner.setPosition(hotspotPos.x, hotspotPos.y, hotspotPos.z);\n // Update listener position to camera\n if (camera && camera.getPosition) {\n const camPos = camera.getPosition();\n const camForward = camera.forward;\n const camUp = camera.up;\n if (audioCtx.listener.positionX) {\n // Modern API\n audioCtx.listener.positionX.value = camPos.x;\n audioCtx.listener.positionY.value = camPos.y;\n audioCtx.listener.positionZ.value = camPos.z;\n audioCtx.listener.forwardX.value = camForward.x;\n audioCtx.listener.forwardY.value = camForward.y;\n audioCtx.listener.forwardZ.value = camForward.z;\n audioCtx.listener.upX.value = camUp.x;\n audioCtx.listener.upY.value = camUp.y;\n audioCtx.listener.upZ.value = camUp.z;\n }\n else {\n // Legacy API\n audioCtx.listener.setPosition(camPos.x, camPos.y, camPos.z);\n audioCtx.listener.setOrientation(camForward.x, camForward.y, camForward.z, camUp.x, camUp.y, camUp.z);\n }\n }\n };\n // Register update function\n app.on('update', updateAudioPosition);\n console.log(`[Audio] Spatial audio setup for hotspot: ${hotspot.title}, refDist=${panner.refDistance}, maxDist=${panner.maxDistance}`);\n return { audio, audioCtx, source, panner, updateAudioPosition };\n }\n else {\n // Non-spatial audio\n console.log(`[Audio] Non-spatial audio setup for hotspot: ${hotspot.title}`);\n return { audio };\n }\n }\n /**\n * Setup spatial audio for video hotspot using Web Audio API\n * Makes the video's audio 3D positional based on hotspot location\n */\n function setupVideoSpatialAudio(entity: pc.Entity, video: HTMLVideoElement, hotspot: HotspotConfigLike): VideoSpatialAudio | null {\n // Check if video should have spatial audio\n if (!hotspot.videoSpatialAudio && hotspot.videoMuted !== false) {\n // No spatial audio requested and video is muted\n return null;\n }\n try {\n // Create AudioContext for spatial audio\n const audioCtx = new (window.AudioContext || (window as WindowWithLegacyAudio).webkitAudioContext)();\n allAudioContexts.push(audioCtx);\n // Create source from video element\n const source = audioCtx.createMediaElementSource(video);\n // Create panner for 3D positioning\n const panner = audioCtx.createPanner();\n panner.panningModel = 'HRTF';\n panner.distanceModel = (hotspot.videoDistanceModel || 'linear') as DistanceModelType; // Match HTML export default\n panner.refDistance = hotspot.videoRefDistance !== undefined ? hotspot.videoRefDistance : 1;\n panner.maxDistance = hotspot.videoMaxDistance !== undefined ? hotspot.videoMaxDistance : 100;\n panner.rolloffFactor = hotspot.videoRolloffFactor !== undefined ? hotspot.videoRolloffFactor : 1;\n // Position panner at hotspot location\n const pos = entity.getPosition();\n panner.setPosition(pos.x, pos.y, pos.z);\n // Connect: source -> panner -> destination\n source.connect(panner);\n panner.connect(audioCtx.destination);\n // Update panner and listener position each frame\n app.on('update', () => {\n if (!entity || !entity.getPosition)\n return;\n // Update panner position to hotspot location\n const hotspotPos = entity.getPosition();\n panner.setPosition(hotspotPos.x, hotspotPos.y, hotspotPos.z);\n // Update listener position to camera\n if (camera && camera.getPosition) {\n const camPos = camera.getPosition();\n const camForward = camera.forward;\n const camUp = camera.up;\n if (audioCtx.listener.positionX) {\n // Modern API\n audioCtx.listener.positionX.value = camPos.x;\n audioCtx.listener.positionY.value = camPos.y;\n audioCtx.listener.positionZ.value = camPos.z;\n audioCtx.listener.forwardX.value = camForward.x;\n audioCtx.listener.forwardY.value = camForward.y;\n audioCtx.listener.forwardZ.value = camForward.z;\n audioCtx.listener.upX.value = camUp.x;\n audioCtx.listener.upY.value = camUp.y;\n audioCtx.listener.upZ.value = camUp.z;\n }\n else {\n // Legacy API\n audioCtx.listener.setPosition(camPos.x, camPos.y, camPos.z);\n audioCtx.listener.setOrientation(camForward.x, camForward.y, camForward.z, camUp.x, camUp.y, camUp.z);\n }\n }\n });\n console.log(`[Audio] Video spatial audio setup for hotspot: ${hotspot.title}, refDist=${panner.refDistance}, maxDist=${panner.maxDistance}`);\n return { audioCtx, source, panner };\n }\n catch (err) {\n console.warn('[Audio] Failed to setup video spatial audio:', err);\n return null;\n }\n }\n /**\n * Setup waypoint audio for audio interactions\n * Matches the HTML export's audioSystem.ts\n */\n function setupWaypointAudio(): void {\n if (!config.waypoints || config.waypoints.length === 0)\n return;\n // Extract audio interactions from waypoints\n config.waypoints.forEach((waypoint, waypointIndex: number) => {\n if (waypoint.interactions && Array.isArray(waypoint.interactions)) {\n waypoint.interactions.forEach((interaction) => {\n if (interaction.type === 'audio' && interaction.data) {\n const data = interaction.data as LegacyWaypointAudioConfig;\n if (!data.url) {\n console.warn(`[Audio] Waypoint ${waypointIndex} audio interaction has no URL, skipping`);\n return;\n }\n const audioId = String(interaction.id || `audio-${waypointIndex}`);\n // Create audio entity\n const audioEntity = new pc.Entity(`waypoint-audio-${audioId}`);\n // Position at waypoint location (with Z negation for PlayCanvas)\n const wpPos = (waypoint.position || { x: 0, y: 0, z: 0 }) as LegacyVec3;\n audioEntity.setPosition(wpPos._x ?? wpPos.x ?? waypoint.x ?? 0, wpPos._y ?? wpPos.y ?? waypoint.y ?? 1.6, -(wpPos._z ?? wpPos.z ?? waypoint.z ?? 0));\n // Configure sound component\n const soundConfig = {\n slots: {\n [audioId]: {\n name: audioId,\n loop: data.loop || false,\n autoPlay: false,\n volume: data.volume !== undefined ? data.volume : 1,\n pitch: 1,\n positional: data.spatialSound || false,\n distanceModel: getPlayCanvasDistanceModel(data.distanceModel || 'exponential'),\n maxDistance: data.maxDistance || 10000,\n refDistance: data.refDistance || 1,\n rollOffFactor: data.rolloffFactor || 1\n }\n }\n };\n audioEntity.addComponent('sound', soundConfig);\n // Load audio asset\n const audioAsset = new pc.Asset(`waypoint-audio-asset-${audioId}`, 'audio', { url: data.url });\n app.assets.add(audioAsset);\n audioAsset.ready(() => {\n const slot = audioEntity.sound?.slot(audioId);\n if (slot) {\n slot.asset = audioAsset.id;\n }\n // Mark as ready for proximity-based playback\n const audioDataRef = waypointAudioMap.get(audioId);\n if (audioDataRef) {\n audioDataRef.assetReady = true;\n }\n console.log(`[Audio] Waypoint audio loaded: ${audioId}, spatialSound=${data.spatialSound}, maxDistance=${data.maxDistance}`);\n });\n app.assets.load(audioAsset);\n // Add to scene\n app.root.addChild(audioEntity);\n // Store audio data\n waypointAudioMap.set(audioId, {\n entity: audioEntity,\n waypointIndex: waypointIndex,\n config: data,\n slotId: audioId,\n playing: false,\n autoplayTriggered: false,\n assetReady: false\n });\n console.log(`[Audio] Waypoint audio created: ${audioId} at waypoint ${waypointIndex}, spatial=${data.spatialSound}`);\n }\n });\n }\n });\n if (waypointAudioMap.size > 0) {\n console.log(`[StorySplat Viewer] Setup ${waypointAudioMap.size} waypoint audio sources`);\n }\n }\n // Map distance model string to PlayCanvas constant\n function getPlayCanvasDistanceModel(modelStr: string): string {\n switch (modelStr) {\n case 'linear':\n return 'linear';\n case 'inverse':\n return 'inverse';\n case 'exponential':\n default:\n return 'exponential';\n }\n }\n // Standalone audio emitters tracking\n interface AudioEmitterData {\n entity: pc.Entity;\n config: AudioEmitterConfig;\n slotId: string;\n playing: boolean;\n assetReady: boolean;\n }\n const audioEmitterMap = new Map<string, AudioEmitterData>();\n /**\n * Setup standalone audio emitters (spatial audio sources not attached to waypoints/hotspots)\n */\n function setupAudioEmitters(): void {\n const emitters = config.audioEmitters;\n if (!emitters || emitters.length === 0)\n return;\n console.log(`[Audio] Setting up ${emitters.length} standalone audio emitters`);\n emitters.forEach((emitter) => {\n if (!emitter.enabled) {\n console.log(`[Audio] Skipping disabled emitter: ${emitter.name || emitter.id}`);\n return;\n }\n if (!emitter.url) {\n console.warn(`[Audio] Emitter has no URL: ${emitter.name || emitter.id}`);\n return;\n }\n const audioId = emitter.id || `emitter-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n const position = emitter.position || { x: 0, y: 0, z: 0 };\n // Create audio entity at emitter position\n const audioEntity = new pc.Entity(`audio-emitter-${audioId}`);\n audioEntity.setPosition(position.x, position.y, position.z);\n // Add sound component with proper spatial settings\n const distanceModel = getPlayCanvasDistanceModel(emitter.distanceModel || 'linear');\n audioEntity.addComponent('sound', {\n positional: emitter.spatialSound !== false,\n refDistance: emitter.refDistance || 1,\n maxDistance: emitter.maxDistance || 100,\n rollOffFactor: emitter.rolloffFactor || 1,\n distanceModel: distanceModel,\n volume: emitter.volume ?? 0.5\n });\n // Add sound slot\n // Note: positional is set on the sound component, not on individual slots\n audioEntity.sound?.addSlot(audioId, {\n volume: emitter.volume ?? 0.5,\n loop: emitter.loop !== false,\n autoPlay: false,\n overlap: false\n });\n // Load audio asset\n const audioAsset = new pc.Asset(`audio-emitter-${audioId}`, 'audio', {\n url: emitter.url\n });\n audioAsset.on('load', () => {\n if (isDestroyed)\n return;\n // Set asset on slot\n const slot = audioEntity.sound?.slot(audioId);\n if (slot) {\n slot.asset = audioAsset.id;\n }\n // Mark as ready\n const emitterData = audioEmitterMap.get(audioId);\n if (emitterData) {\n emitterData.assetReady = true;\n // Start playing if autoplay is enabled\n if (emitter.autoplay !== false && slot) {\n console.log(`[Audio] Autoplay emitter: ${emitter.name || audioId}`);\n slot.play();\n emitterData.playing = true;\n }\n }\n console.log(`[Audio] Emitter loaded: ${emitter.name || audioId}, spatial=${emitter.spatialSound !== false}, maxDistance=${emitter.maxDistance || 100}`);\n });\n app.assets.add(audioAsset);\n app.assets.load(audioAsset);\n // Add to scene\n app.root.addChild(audioEntity);\n // Store emitter data\n audioEmitterMap.set(audioId, {\n entity: audioEntity,\n config: emitter,\n slotId: audioId,\n playing: false,\n assetReady: false\n });\n console.log(`[Audio] Emitter created: ${emitter.name || audioId} at (${position.x}, ${position.y}, ${position.z})`);\n });\n if (audioEmitterMap.size > 0) {\n console.log(`[StorySplat Viewer] Setup ${audioEmitterMap.size} standalone audio emitters`);\n }\n }\n /**\n * Update audio emitter proximity for spatial audio (called every frame)\n */\n function updateAudioEmitterProximity(): void {\n if (audioEmitterMap.size === 0)\n return;\n const cameraPos = camera.getPosition();\n audioEmitterMap.forEach((emitterData, audioId) => {\n const { entity, config, slotId, assetReady, playing } = emitterData;\n // Skip if asset not ready\n if (!assetReady)\n return;\n const slot = entity.sound?.slot(slotId);\n if (!slot)\n return;\n // Only do proximity checks for spatial audio\n if (config.spatialSound !== false) {\n const emitterPos = entity.getPosition();\n const distance = cameraPos.distance(emitterPos);\n const maxDistance = config.maxDistance || 100;\n // Start playing when entering proximity (if not already playing)\n if (distance <= maxDistance && !playing && config.autoplay !== false) {\n if (!slot.isPlaying) {\n slot.play();\n emitterData.playing = true;\n }\n }\n }\n });\n }\n /**\n * Update waypoint audio based on current waypoint (for autoplay on waypoint entry)\n * Called when waypoint changes\n */\n function updateWaypointAudio(currentIndex: number, previousIndex: number): void {\n waypointAudioMap.forEach((audioData, audioId) => {\n const { entity, waypointIndex, config, slotId, autoplayTriggered } = audioData;\n const slot = entity.sound?.slot(slotId);\n if (!slot)\n return;\n // Check if we're entering this waypoint\n const isEntering = currentIndex === waypointIndex && previousIndex !== waypointIndex;\n // Check if we're leaving this waypoint\n const isLeaving = previousIndex === waypointIndex && currentIndex !== waypointIndex;\n // Handle autoplay on waypoint entry (only if autoplay is explicitly true)\n if (isEntering && config.autoplay && !autoplayTriggered) {\n console.log(`[Audio] Autoplay waypoint audio: ${audioId} at waypoint ${waypointIndex}`);\n if (!slot.isPlaying) {\n slot.play();\n audioData.playing = true;\n audioData.autoplayTriggered = true;\n }\n }\n // Handle stop on waypoint exit\n if (isLeaving && config.stopOnExit && audioData.playing) {\n console.log(`[Audio] Stopping waypoint audio on exit: ${audioId}`);\n if (slot.isPlaying) {\n slot.stop();\n audioData.playing = false;\n audioData.autoplayTriggered = false;\n }\n }\n });\n }\n /**\n * Update waypoint audio based on PROXIMITY to audio source\n * For spatial audio, plays when camera is within maxDistance of the audio source\n * Called every frame\n */\n function updateWaypointAudioProximity(): void {\n if (waypointAudioMap.size === 0)\n return;\n const cameraPos = camera.getPosition();\n waypointAudioMap.forEach((audioData, audioId) => {\n const { entity, config, slotId, assetReady, playing } = audioData;\n // Skip if asset not ready yet\n if (!assetReady)\n return;\n const slot = entity.sound?.slot(slotId);\n if (!slot)\n return;\n // For spatial audio, use proximity-based playback\n if (config.spatialSound) {\n const audioPos = entity.getPosition();\n const distance = cameraPos.distance(audioPos);\n const maxDistance = config.maxDistance || 20;\n // Play when entering proximity (within maxDistance)\n if (distance <= maxDistance && !playing) {\n console.log(`[Audio] Proximity play: ${audioId}, distance=${distance.toFixed(2)}, maxDistance=${maxDistance}`);\n slot.play();\n audioData.playing = true;\n }\n // Stop when leaving proximity (only if stopOnExit is true)\n else if (distance > maxDistance && playing && config.stopOnExit) {\n console.log(`[Audio] Proximity stop: ${audioId}, distance=${distance.toFixed(2)}`);\n slot.stop();\n audioData.playing = false;\n }\n }\n });\n }\n // Track which waypoints are \"active\" (camera within trigger distance)\n const activeWaypointTriggers = new Set<number>();\n /**\n * Check waypoint trigger distances and execute/reverse interactions\n * Called every frame in the update loop\n */\n function checkWaypointTriggerDistance(): void {\n if (!config.waypoints?.length)\n return;\n const cameraPos = camera.getPosition();\n config.waypoints.forEach((wp, index: number) => {\n const wpPos = new pc.Vec3(wp.position?.x ?? 0, wp.position?.y ?? 0, -(wp.position?.z ?? 0) // Z-axis negation for PlayCanvas\n );\n const distance = cameraPos.distance(wpPos);\n const triggerDist = wp.triggerDistance ?? 1.0;\n if (distance <= triggerDist) {\n // Entering trigger zone\n if (!activeWaypointTriggers.has(index)) {\n activeWaypointTriggers.add(index);\n console.log(`[StorySplat] Waypoint ${index} triggered (distance: ${distance.toFixed(2)}, threshold: ${triggerDist})`);\n executeWaypointInteractions(wp, index);\n }\n }\n else {\n // Exiting trigger zone\n if (activeWaypointTriggers.has(index)) {\n activeWaypointTriggers.delete(index);\n console.log(`[StorySplat] Waypoint ${index} exited`);\n reverseWaypointInteractions(wp, index);\n }\n }\n });\n }\n /**\n * Execute all interactions on a waypoint when entering trigger distance\n */\n function executeWaypointInteractions(waypoint: WaypointConfigLike, waypointIndex: number): void {\n if (!waypoint.interactions?.length)\n return;\n waypoint.interactions.forEach((interaction) => {\n if (interaction.type === 'audio') {\n // The audio ID matches interaction.id (set in setupWaypointAudio)\n const audioId = interaction.id;\n if (!audioId) return;\n const audioData = waypointAudioMap.get(audioId);\n if (audioData && audioData.assetReady && !audioData.playing) {\n const slot = audioData.entity.sound?.slot(audioData.slotId);\n if (slot) {\n slot.play();\n audioData.playing = true;\n }\n }\n }\n // Info interactions would show popup (if implemented)\n });\n }\n /**\n * Reverse/stop interactions when exiting trigger distance\n */\n function reverseWaypointInteractions(waypoint: WaypointConfigLike, waypointIndex: number): void {\n if (!waypoint.interactions?.length)\n return;\n waypoint.interactions.forEach((interaction) => {\n if (interaction.type === 'audio') {\n const stopOnExit = interaction.data?.stopOnExit ?? false;\n if (stopOnExit) {\n // The audio ID matches interaction.id (set in setupWaypointAudio)\n const audioId = interaction.id;\n if (!audioId) return;\n const audioData = waypointAudioMap.get(audioId);\n if (audioData && audioData.playing) {\n const slot = audioData.entity.sound?.slot(audioData.slotId);\n if (slot) {\n slot.stop();\n audioData.playing = false;\n }\n }\n }\n }\n // Info interactions would hide popup (if implemented)\n });\n }\n /**\n * Global mute/unmute functions\n */\n function muteAllAudio(): void {\n if (globalMuted)\n return;\n // Mute hotspot audio elements\n allAudioElements.forEach(audio => {\n storedVolumes.set(audio, audio.volume);\n audio.volume = 0;\n });\n // Mute waypoint audio\n waypointAudioMap.forEach((audioData) => {\n const slot = audioData.entity.sound?.slot(audioData.slotId);\n if (slot) {\n (slot as AudioSlotWithStoredVolume)._storedVolume = slot.volume;\n slot.volume = 0;\n }\n });\n // Mute audio emitters\n audioEmitterMap.forEach((emitterData) => {\n const slot = emitterData.entity.sound?.slot(emitterData.slotId);\n if (slot) {\n (slot as AudioSlotWithStoredVolume)._storedVolume = slot.volume;\n slot.volume = 0;\n }\n });\n // Also mute any HTML audio/video elements\n document.querySelectorAll('audio, video').forEach((el) => {\n (el as HTMLMediaElement).muted = true;\n });\n globalMuted = true;\n console.log('[Audio] All audio muted');\n }\n function unmuteAllAudio(): void {\n if (!globalMuted)\n return;\n // Unmute hotspot audio elements\n allAudioElements.forEach(audio => {\n const storedVol = storedVolumes.get(audio);\n audio.volume = storedVol !== undefined ? storedVol : 1;\n });\n // Unmute waypoint audio\n waypointAudioMap.forEach((audioData) => {\n const slot = audioData.entity.sound?.slot(audioData.slotId);\n if (slot) {\n const storedVol = (slot as AudioSlotWithStoredVolume)._storedVolume;\n slot.volume = storedVol !== undefined ? storedVol : 1;\n }\n });\n // Unmute audio emitters\n audioEmitterMap.forEach((emitterData) => {\n const slot = emitterData.entity.sound?.slot(emitterData.slotId);\n if (slot) {\n const storedVol = (slot as AudioSlotWithStoredVolume)._storedVolume;\n slot.volume = storedVol !== undefined ? storedVol : 1;\n }\n });\n // Also unmute any HTML audio/video elements\n document.querySelectorAll('audio, video').forEach((el) => {\n (el as HTMLMediaElement).muted = false;\n });\n globalMuted = false;\n console.log('[Audio] All audio unmuted');\n }\n function toggleMuteAll(): boolean {\n if (globalMuted) {\n unmuteAllAudio();\n }\n else {\n muteAllAudio();\n }\n return globalMuted;\n }\n // Track animated GIF textures for cleanup\n const animatedGifs: AnimatedGifTexture[] = [];\n // Create hotspot material for images - entity is hidden until texture loads\n // Supports both static images and animated GIFs with transparency\n function createImageMaterial(imageUrl: string, useLighting: boolean = false, opacity: number = 1, onTextureReady?: () => void): pc.StandardMaterial {\n const material = new pc.StandardMaterial();\n // Configure material for transparency with proper depth handling\n // Use premultiplied alpha blending with depth write for better GSplat compatibility\n material.blendType = pc.BLEND_PREMULTIPLIED;\n material.depthTest = true; // Enable depth testing so hotspots are occluded by splats\n material.depthWrite = true; // Enable depth write for proper GSplat sorting\n material.cull = pc.CULLFACE_NONE; // Double-sided rendering\n material.twoSidedLighting = true; // Proper lighting on both sides\n material.alphaTest = 0.01; // Discard nearly transparent pixels for better sorting\n // For unlit mode: disable all lighting calculations, use only emissive\n if (!useLighting) {\n // Disable diffuse lighting entirely\n material.diffuse = new pc.Color(0, 0, 0);\n // Full white emissive - texture will provide color\n material.emissive = new pc.Color(1, 1, 1);\n // Disable specular\n material.specular = new pc.Color(0, 0, 0);\n material.useLighting = false;\n }\n else {\n material.diffuse = new pc.Color(1, 1, 1);\n }\n material.opacity = opacity;\n material.update();\n // Check if this is a GIF - use AnimatedGifTexture for proper transparency and animation\n if (isGifUrl(imageUrl)) {\n console.log(`[Hotspot] Loading animated GIF: ${imageUrl}`);\n const gifTexture = new AnimatedGifTexture(app, imageUrl, {\n autoPlay: true,\n onReady: () => {\n if (isDestroyed) {\n console.log(`[Hotspot] Ignoring GIF load - viewer was destroyed: ${imageUrl}`);\n gifTexture.destroy();\n return;\n }\n if (gifTexture.texture) {\n gifTexture.texture.premultiplyAlpha = true;\n // Assign texture to material based on lighting mode\n if (!useLighting) {\n material.emissiveMap = gifTexture.texture;\n material.opacityMap = gifTexture.texture;\n }\n else {\n material.diffuseMap = gifTexture.texture;\n material.opacityMap = gifTexture.texture;\n }\n material.opacityMapChannel = 'a';\n material.update();\n console.log(`[Hotspot] GIF texture loaded: ${imageUrl}, useLighting=${useLighting}`);\n if (onTextureReady) {\n onTextureReady();\n }\n }\n },\n onError: (error) => {\n console.error(`[Hotspot] Failed to load GIF: ${imageUrl}`, error);\n // On error, make it slightly visible so user knows something is there\n material.opacity = 0.3;\n material.emissive = new pc.Color(1, 0, 0); // Red to indicate error\n material.update();\n if (onTextureReady) {\n onTextureReady(); // Still call callback so entity becomes visible\n }\n }\n });\n // Track for cleanup\n animatedGifs.push(gifTexture);\n }\n else {\n // Standard image loading\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => {\n // Check if viewer was destroyed while image was loading (React StrictMode race condition)\n if (isDestroyed) {\n console.log(`[Hotspot] Ignoring texture load - viewer was destroyed: ${imageUrl}`);\n return;\n }\n // Create texture with the app's graphics device\n const texture = new pc.Texture(app.graphicsDevice, {\n width: img.width,\n height: img.height,\n format: pc.PIXELFORMAT_RGBA8,\n mipmaps: true,\n minFilter: pc.FILTER_LINEAR_MIPMAP_LINEAR,\n magFilter: pc.FILTER_LINEAR,\n addressU: pc.ADDRESS_CLAMP_TO_EDGE,\n addressV: pc.ADDRESS_CLAMP_TO_EDGE\n });\n // Upload the image data\n texture.setSource(img);\n texture.premultiplyAlpha = true;\n // Assign texture to material based on lighting mode\n if (!useLighting) {\n // Unlit: only use emissive map (no diffuse lighting)\n material.emissiveMap = texture;\n material.opacityMap = texture;\n }\n else {\n // Lit: use diffuse map\n material.diffuseMap = texture;\n material.opacityMap = texture;\n }\n material.opacityMapChannel = 'a';\n material.update();\n console.log(`[Hotspot] Texture loaded for: ${imageUrl}, useLighting=${useLighting}`);\n if (onTextureReady) {\n onTextureReady();\n }\n };\n img.onerror = (err) => {\n console.error(`[Hotspot] Failed to load texture: ${imageUrl}`, err);\n // On error, make it slightly visible so user knows something is there\n material.opacity = 0.3;\n material.emissive = new pc.Color(1, 0, 0); // Red to indicate error\n material.update();\n if (onTextureReady) {\n onTextureReady(); // Still call callback so entity becomes visible\n }\n };\n img.src = imageUrl;\n }\n return material;\n }\n /**\n * Apply composed rotation for plane entities (image/video/gif hotspots & portals).\n * PlayCanvas planes are horizontal (XZ) by default, so we need a 90° X rotation\n * to make them face forward. This must be composed WITH the user rotation, not\n * applied after it in local space (which would rotate in the wrong frame of\n * reference when the user rotation is non-zero).\n *\n * Matches BabylonJS behavior where the plane naturally faces forward and the\n * user rotation is on a parent TransformNode.\n */\n /**\n * Convert BabylonJS Euler angles (radians, YXZ intrinsic order, left-handed)\n * to a PlayCanvas quaternion (right-handed).\n *\n * BabylonJS mesh.rotation uses YXZ intrinsic order (RotationYawPitchRoll).\n * PlayCanvas setFromEulerAngles uses XYZ intrinsic order — these differ,\n * so passing angles directly causes wrong results when multiple axes are\n * non-zero. Instead we build the quaternion with the BJS formula, then\n * convert handedness by negating qx and qy (Z-axis flip).\n */\n function bjsRotationToPC(rx: number, ry: number, rz: number): pc.Quat {\n // BJS RotationYawPitchRoll: yaw=ry, pitch=rx, roll=rz\n const hy = ry / 2, hx = rx / 2, hz = rz / 2;\n const cy = Math.cos(hy), sy = Math.sin(hy);\n const cx = Math.cos(hx), sx = Math.sin(hx);\n const cz = Math.cos(hz), sz = Math.sin(hz);\n // Quaternion in BJS left-handed space (YXZ order)\n const qw = cy * cx * cz + sy * sx * sz;\n const qx = cy * sx * cz + sy * cx * sz;\n const qy = sy * cx * cz - cy * sx * sz;\n const qz = cy * cx * sz - sy * sx * cz;\n // Convert LH → RH (Z-flip): negate x and y components\n return new pc.Quat(-qx, -qy, qz, qw);\n }\n\n /**\n * Apply rotation to plane entities (image/video/gif hotspots & portals).\n * PlayCanvas planes are horizontal (XZ, normal +Y) by default.\n * BabylonJS planes face +Z (vertical, normal toward camera).\n * A 90° X rotation in PC space tilts the plane upright to face -Z\n * (PC forward = BJS +Z after the handedness flip).\n * The user rotation is composed on top via proper quaternion math.\n */\n function applyPlaneRotation(entity: pc.Entity, rxRad: number, ryRad: number, rzRad: number): void {\n const faceForward = new pc.Quat().setFromEulerAngles(90, 0, 0);\n const userRot = bjsRotationToPC(rxRad, ryRad, rzRad);\n entity.setRotation(new pc.Quat().mul2(userRot, faceForward));\n }\n\n /**\n * Create a single hotspot entity with full rendering (sphere, image, video, gif, billboard, etc.)\n * Used by both createHotspots() at init and the editor mutation API.\n */\n function createSingleHotspot(hotspot: HotspotConfigLike, index: number): pc.Entity {\n const entity = new pc.Entity(`hotspot-${hotspot.id || index}`);\n // Get position (negate Z for BabylonJS -> PlayCanvas coordinate conversion)\n const pos = hotspot.position || { _x: 0, _y: 0, _z: 0 };\n entity.setPosition(pos._x ?? pos.x ?? 0, pos._y ?? pos.y ?? 0, -(pos._z ?? pos.z ?? 0));\n // Get scale\n const rawScale = hotspot.scale || { _x: 1, _y: 1, _z: 1 };\n const scaleObj = typeof rawScale === 'number' ? { x: rawScale, y: rawScale, z: rawScale } as LegacyVec3 : rawScale;\n const sx = Math.abs(scaleObj._x ?? scaleObj.x ?? 1);\n const sy = Math.abs(scaleObj._y ?? scaleObj.y ?? 1);\n const sz = scaleObj._z ?? scaleObj.z ?? 1;\n // Get rotation in radians (BabylonJS stores Euler angles in radians, YXZ order)\n const rot = hotspot.rotation || { _x: 0, _y: 0, _z: 0 };\n const rotXRad = rot._x ?? rot.x ?? 0;\n const rotYRad = rot._y ?? rot.y ?? 0;\n const rotZRad = rot._z ?? rot.z ?? 0;\n // Convert BJS rotation to PC quaternion (handles YXZ→RH conversion properly)\n entity.setRotation(bjsRotationToPC(rotXRad, rotYRad, rotZRad));\n // Create geometry based on type\n if (hotspot.type === 'sphere') {\n entity.addComponent('render', {\n type: 'sphere',\n castShadows: false,\n receiveShadows: false\n });\n const material = new pc.StandardMaterial();\n const color = parseColor(hotspot.color || '#ffffff');\n material.diffuse = color;\n material.emissive = color.clone();\n (material.emissive as pc.Color).mulScalar(0.5);\n // For spheres: use full opacity with depth write for proper GSplat occlusion\n // GSplat doesn't sort properly with transparent objects, so make spheres opaque-ish\n const sphereOpacity = hotspot.opacity ?? 0.8;\n if (sphereOpacity >= 0.95) {\n // Fully opaque - use standard opaque rendering\n material.opacity = 1;\n material.blendType = pc.BLEND_NONE;\n material.depthTest = true;\n material.depthWrite = true;\n }\n else {\n // Semi-transparent - use additive blending for better GSplat compatibility\n material.opacity = sphereOpacity;\n material.blendType = pc.BLEND_ADDITIVEALPHA;\n material.depthTest = true;\n material.depthWrite = true; // Write depth to help with sorting\n }\n material.update();\n entity.render!.material = material;\n entity.setLocalScale(sx * 0.2, sy * 0.2, sz * 0.2); // Scale down spheres\n }\n else if (hotspot.type === 'image' && hotspot.imageUrl) {\n entity.addComponent('render', {\n type: 'plane',\n castShadows: false,\n receiveShadows: false\n });\n // Get initial opacity (use startOpacity for animated, opacity for static)\n // Default to 1 (fully visible) - user must explicitly set startOpacity to 0 for fade-in\n let initialOpacity = 1;\n if (hotspot.opacityMode === 'animated' && hotspot.opacityAnimation) {\n // Use startOpacity if defined, otherwise default to 1 (visible)\n initialOpacity = hotspot.opacityAnimation.startOpacity !== undefined\n ? hotspot.opacityAnimation.startOpacity\n : 1;\n }\n else if (hotspot.opacity !== undefined) {\n initialOpacity = hotspot.opacity;\n }\n // Store target opacity for animation system\n (entity as ViewerRuntimeEntity).targetOpacity = initialOpacity;\n (entity as ViewerRuntimeEntity).textureLoaded = false;\n // HIDE entity until texture is loaded to prevent WebGL errors\n (entity as ViewerRuntimeEntity).hiddenUntilTextureLoaded = true;\n entity.enabled = false;\n // Check useLighting setting from hotspot (default to false = unlit)\n const useLighting = hotspot.useLighting === true;\n const material = createImageMaterial(hotspot.imageUrl, useLighting, initialOpacity, () => {\n // Callback when texture is ready - show the entity\n (entity as ViewerRuntimeEntity).textureLoaded = true;\n // Only enable if not hidden by visibility range\n if (!(entity as ViewerRuntimeEntity).visibilityRange || (entity as ViewerRuntimeEntity).shouldBeVisible) {\n entity.enabled = true;\n }\n (entity as ViewerRuntimeEntity).hiddenUntilTextureLoaded = false;\n });\n entity.render!.material = material;\n entity.setLocalScale(sx, sz, sy);\n // Compose face-forward correction with user rotation (fixes 90° offset for non-zero rotations)\n applyPlaneRotation(entity, rotXRad, rotYRad, rotZRad);\n // Store material reference for opacity animation\n (entity as ViewerRuntimeEntity).hotspotMaterial = material;\n console.log(`[Hotspot] Created image hotspot: ${hotspot.title}, opacity=${initialOpacity}, opacityMode=${hotspot.opacityMode}, useLighting=${useLighting}`);\n }\n else if (hotspot.type === 'video' && hotspot.videoUrl) {\n entity.addComponent('render', {\n type: 'plane',\n castShadows: false,\n receiveShadows: false\n });\n // Detect iOS for alpha video method\n const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);\n // Determine which video URLs to use (iOS alpha support)\n const useAlphaMethod = (isIOS && hotspot.useIOSVideoAlphaMethod) || hotspot.forceIOSVideoAlphaMethodForAllDevices;\n const mainVideoUrl = useAlphaMethod && hotspot.iosMainVideoUrl ? hotspot.iosMainVideoUrl : hotspot.videoUrl;\n const alphaVideoUrl = useAlphaMethod ? (hotspot.alphaMaskVideoUrl || null) : null;\n // Detect if URL is a WebM file (which may contain alpha)\n const isWebMUrl = (url: string): boolean => {\n const lower = url.toLowerCase();\n return lower.endsWith('.webm') || lower.includes('format=webm') || lower.includes('video/webm');\n };\n // Check if this video is WebM (may have embedded alpha)\n const mainIsWebM = isWebMUrl(mainVideoUrl);\n const hasWebMAlpha = mainIsWebM && (hotspot.webmHasAlpha !== false); // Default true for WebM\n // Helper to create video element and texture\n const createVideoAndTexture = (url: string, isAlpha: boolean, useRGBA: boolean = false) => {\n const v = document.createElement('video');\n v.src = url;\n v.loop = hotspot.videoLoop !== false;\n v.crossOrigin = 'anonymous';\n v.playsInline = true;\n v.preload = 'metadata'; // Load metadata + first frame\n // Default muted state\n if (isAlpha) {\n v.muted = true; // Alpha video always muted\n }\n else {\n v.muted = hotspot.videoMuted !== false;\n }\n // Autoplay handling\n if (hotspot.mediaTriggerMode === 'autoplay') {\n v.autoplay = true;\n v.muted = true; // Autoplay requires muted (browser policy)\n }\n // Use RGBA format for WebM (alpha support) or standard RGB\n const t = new pc.Texture(app.graphicsDevice, {\n format: useRGBA ? pc.PIXELFORMAT_R8_G8_B8_A8 : pc.PIXELFORMAT_R8_G8_B8,\n mipmaps: false,\n minFilter: pc.FILTER_LINEAR,\n magFilter: pc.FILTER_LINEAR,\n addressU: pc.ADDRESS_CLAMP_TO_EDGE,\n addressV: pc.ADDRESS_CLAMP_TO_EDGE\n });\n t.setSource(v);\n return { video: v, texture: t };\n };\n // Create main video and texture - use RGBA for WebM to support embedded alpha\n const main = createVideoAndTexture(mainVideoUrl, false, hasWebMAlpha);\n const video = main.video;\n const videoTexture = main.texture;\n // Add fallback to backup video URL if main fails (codec compatibility)\n if (hotspot.videoBackupUrl) {\n const backupUrl = hotspot.videoBackupUrl;\n video.onerror = () => {\n console.log(`[Hotspot] Main video failed, trying backup: ${backupUrl}`);\n video.src = backupUrl;\n video.load();\n };\n }\n // Create material - unlit (emissive only, no diffuse lighting)\n // Video should display as-is without scene lighting affecting brightness\n const material = new pc.StandardMaterial();\n material.useLighting = false;\n material.emissiveMap = videoTexture;\n material.emissive = new pc.Color(1, 1, 1);\n material.diffuse = new pc.Color(0, 0, 0);\n // Enable depth testing so hotspots are occluded by splats\n // Use depthWrite=true with premultiplied alpha for proper GSplat depth sorting\n material.depthTest = true;\n material.depthWrite = true;\n material.cull = pc.CULLFACE_NONE; // Double-sided rendering\n material.twoSidedLighting = true; // Proper lighting on both sides\n // Default blend type for non-transparent videos\n material.blendType = pc.BLEND_PREMULTIPLIED;\n material.alphaTest = 0.01;\n // WebM with embedded alpha - configure for transparency\n if (hasWebMAlpha) {\n material.opacityMap = videoTexture; // Alpha channel from same texture\n material.blendType = pc.BLEND_PREMULTIPLIED;\n material.alphaTest = 0.01;\n console.log(`[Hotspot] WebM video with alpha enabled: ${hotspot.title}`);\n }\n // Alpha video support (iOS dual-video method) - takes precedence over WebM embedded alpha\n // iOS uses a separate grayscale video where luminance = alpha (chroma key technique)\n let alphaVideo: HTMLVideoElement | null = null;\n let alphaTexture: pc.Texture | null = null;\n if (alphaVideoUrl) {\n const alpha = createVideoAndTexture(alphaVideoUrl, true, false);\n alphaVideo = alpha.video;\n alphaTexture = alpha.texture;\n material.opacityMap = alphaTexture;\n // CRITICAL: Use 'r' channel for opacity since iOS alpha videos store\n // alpha as grayscale luminance in RGB, not in actual alpha channel\n // This matches HTML export's getAlphaFromRGB = true behavior\n material.opacityMapChannel = 'r';\n material.blendType = pc.BLEND_PREMULTIPLIED;\n material.alphaTest = 0.01;\n // Synchronize alpha video with main video\n video.addEventListener('play', () => {\n if (alphaVideo && alphaVideo.paused) {\n alphaVideo.currentTime = video.currentTime;\n alphaVideo.play().catch(console.warn);\n }\n });\n video.addEventListener('pause', () => {\n if (alphaVideo && !alphaVideo.paused) {\n alphaVideo.pause();\n }\n });\n video.addEventListener('seeked', () => {\n if (alphaVideo) {\n alphaVideo.currentTime = video.currentTime;\n }\n });\n console.log(`[Hotspot] iOS alpha mask video enabled: ${hotspot.title}, alphaUrl=${alphaVideoUrl.substring(0, 50)}...`);\n }\n material.update();\n // Update textures each frame\n app.on('update', () => {\n if (video.readyState === video.HAVE_ENOUGH_DATA) {\n videoTexture.upload();\n }\n if (alphaVideo && alphaTexture && alphaVideo.readyState === alphaVideo.HAVE_ENOUGH_DATA) {\n alphaTexture.upload();\n }\n });\n // Aspect ratio scaling from video metadata\n const initialScale = { x: sx, y: sy, z: sz };\n video.addEventListener('loadedmetadata', () => {\n const width = video.videoWidth;\n const height = video.videoHeight;\n if (width > 0 && height > 0) {\n const ratio = width / height;\n // Only auto-adjust if scale is default (1,1)\n if (initialScale.x === 1 && initialScale.y === 1) {\n entity.setLocalScale(ratio * initialScale.y, initialScale.z, initialScale.y);\n }\n }\n });\n entity.render!.material = material;\n entity.setLocalScale(sx, sz, sy);\n // Compose face-forward correction with user rotation\n applyPlaneRotation(entity, rotXRad, rotYRad, rotZRad);\n // Store video references for playback control\n (entity as ViewerRuntimeEntity).videoElement = video;\n (entity as ViewerRuntimeEntity).alphaVideoElement = alphaVideo;\n (entity as ViewerRuntimeEntity).hotspotMaterial = material;\n // Store media trigger mode and proximity settings\n (entity as ViewerRuntimeEntity).mediaTriggerMode = hotspot.mediaTriggerMode || 'click';\n (entity as ViewerRuntimeEntity).proximityDistance = hotspot.proximityDistance || 5;\n (entity as ViewerRuntimeEntity).isVideoPlaying = false;\n // Force first frame display for click-triggered videos (matching HTML export behavior)\n // loadeddata fires when enough data is available to render the first frame\n if (hotspot.mediaTriggerMode === 'click' || !hotspot.mediaTriggerMode) {\n video.addEventListener('loadeddata', () => {\n // Seek to start to ensure first frame is decoded\n video.currentTime = 0;\n // Force texture upload to show the first frame on the plane\n videoTexture.upload();\n if (alphaVideo && alphaTexture) {\n alphaVideo.currentTime = 0;\n alphaTexture.upload();\n }\n console.log(`[Hotspot] First frame loaded for: ${hotspot.title}`);\n }, { once: true });\n // Trigger loading (some browsers need explicit load() call)\n video.load();\n if (alphaVideo) alphaVideo.load();\n }\n // Setup spatial audio for video if not muted\n if (hotspot.videoMuted !== true) {\n const videoSpatialAudio = setupVideoSpatialAudio(entity, video, hotspot);\n if (videoSpatialAudio) {\n (entity as ViewerRuntimeEntity).videoSpatialAudio = videoSpatialAudio;\n }\n }\n // Create \"Tap to Start\" overlay for click-triggered videos (matching HTML export)\n if (hotspot.mediaTriggerMode === 'click' || !hotspot.mediaTriggerMode || (hotspot.mediaTriggerMode === 'autoplay' && hotspot.videoMuted === false)) {\n const overlayEntity = new pc.Entity('video-overlay-' + (hotspot.id || index));\n overlayEntity.addComponent('render', {\n type: 'plane',\n castShadows: false,\n receiveShadows: false\n });\n // Position slightly in front of the video plane\n overlayEntity.setLocalPosition(0, 0, -0.01);\n overlayEntity.setLocalScale(1, 1, 1);\n // Create overlay texture with play icon and text\n const overlayCanvas = document.createElement('canvas');\n overlayCanvas.width = 512;\n overlayCanvas.height = 512;\n const octx = overlayCanvas.getContext('2d')!;\n // Clear to transparent\n octx.clearRect(0, 0, 512, 512);\n // Semi-transparent dark background\n octx.fillStyle = 'rgba(0, 0, 0, 0.4)';\n octx.fillRect(0, 0, 512, 512);\n // Draw play triangle icon\n octx.fillStyle = 'rgba(255, 255, 255, 0.9)';\n octx.beginPath();\n octx.moveTo(220, 180);\n octx.lineTo(220, 300);\n octx.lineTo(320, 240);\n octx.closePath();\n octx.fill();\n // Draw text\n octx.font = 'bold 36px sans-serif';\n octx.textAlign = 'center';\n octx.textBaseline = 'middle';\n octx.shadowColor = 'rgba(0, 0, 0, 0.8)';\n octx.shadowBlur = 8;\n octx.shadowOffsetX = 2;\n octx.shadowOffsetY = 2;\n octx.fillStyle = 'white';\n const overlayText = (hotspot.mediaTriggerMode === 'autoplay' && hotspot.videoMuted === false) ? 'Tap for Audio' : 'Tap to Start';\n octx.fillText(overlayText, 256, 350);\n const overlayTexture = new pc.Texture(app.graphicsDevice, {\n format: pc.PIXELFORMAT_R8_G8_B8_A8,\n mipmaps: false,\n minFilter: pc.FILTER_LINEAR,\n magFilter: pc.FILTER_LINEAR,\n addressU: pc.ADDRESS_CLAMP_TO_EDGE,\n addressV: pc.ADDRESS_CLAMP_TO_EDGE\n });\n overlayTexture.setSource(overlayCanvas);\n const overlayMat = new pc.StandardMaterial();\n overlayMat.useLighting = false;\n overlayMat.emissive = new pc.Color(1, 1, 1);\n overlayMat.emissiveMap = overlayTexture;\n overlayMat.diffuse = new pc.Color(0, 0, 0);\n overlayMat.opacityMap = overlayTexture;\n overlayMat.blendType = pc.BLEND_PREMULTIPLIED;\n overlayMat.alphaTest = 0.01;\n overlayMat.depthTest = true;\n overlayMat.depthWrite = false;\n overlayMat.cull = pc.CULLFACE_NONE;\n overlayMat.update();\n overlayEntity.render!.material = overlayMat;\n entity.addChild(overlayEntity);\n // Update overlay visibility based on video state\n const updateOverlayVisibility = () => {\n if (hotspot.mediaTriggerMode === 'autoplay' && hotspot.videoMuted === false) {\n overlayEntity.enabled = video.muted;\n } else {\n overlayEntity.enabled = video.paused;\n }\n };\n video.addEventListener('play', updateOverlayVisibility);\n video.addEventListener('pause', updateOverlayVisibility);\n video.addEventListener('volumechange', updateOverlayVisibility);\n // Initially visible (video starts paused)\n overlayEntity.enabled = true;\n (entity as ViewerRuntimeEntity).videoOverlay = overlayEntity;\n console.log(`[Hotspot] Created tap-to-play overlay for: ${hotspot.title}`);\n }\n console.log(`[Hotspot] Created video hotspot: ${hotspot.title}, mode=${hotspot.mediaTriggerMode}, useAlpha=${!!alphaVideoUrl}, spatialAudio=${!!(entity as ViewerRuntimeEntity).videoSpatialAudio}`);\n }\n else if (hotspot.type === 'gif' && hotspot.gifUrl) {\n // GIF hotspot implementation - animated texture using canvas\n entity.addComponent('render', {\n type: 'plane',\n castShadows: false,\n receiveShadows: false\n });\n // Get initial opacity\n let initialOpacity = 1;\n if (hotspot.opacityMode === 'animated' && hotspot.opacityAnimation) {\n initialOpacity = hotspot.opacityAnimation.startOpacity !== undefined\n ? hotspot.opacityAnimation.startOpacity\n : 1;\n }\n else if (hotspot.opacity !== undefined) {\n initialOpacity = hotspot.opacity;\n }\n // Store state for GIF animation\n (entity as ViewerRuntimeEntity).targetOpacity = initialOpacity;\n (entity as ViewerRuntimeEntity).textureLoaded = false;\n (entity as ViewerRuntimeEntity).hiddenUntilTextureLoaded = true;\n entity.enabled = false;\n // Check useLighting setting from hotspot (default to false = unlit)\n const useLighting = hotspot.useLighting === true;\n // Create material for GIF\n // Use BLEND_PREMULTIPLIED with depthWrite=true for proper GSplat depth sorting\n const material = new pc.StandardMaterial();\n material.blendType = pc.BLEND_PREMULTIPLIED;\n material.opacity = initialOpacity;\n material.depthTest = true;\n material.depthWrite = true;\n material.cull = pc.CULLFACE_NONE;\n material.twoSidedLighting = true;\n material.alphaTest = 0.01;\n if (!useLighting) {\n material.emissive = new pc.Color(1, 1, 1);\n material.diffuse = new pc.Color(0, 0, 0);\n }\n // Load GIF using SuperGif library pattern (parse frames)\n const loadAnimatedGif = async (gifUrl: string) => {\n try {\n // Create a canvas for GIF rendering\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d')!;\n // Load GIF as image first to get dimensions\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => {\n canvas.width = img.width || 256;\n canvas.height = img.height || 256;\n // Draw static frame initially\n ctx.drawImage(img, 0, 0);\n // Create texture from canvas\n const texture = new pc.Texture(app.graphicsDevice, {\n format: pc.PIXELFORMAT_R8_G8_B8_A8,\n mipmaps: false,\n minFilter: pc.FILTER_LINEAR,\n magFilter: pc.FILTER_LINEAR,\n addressU: pc.ADDRESS_CLAMP_TO_EDGE,\n addressV: pc.ADDRESS_CLAMP_TO_EDGE\n });\n texture.setSource(canvas);\n material.diffuseMap = texture;\n if (!useLighting) {\n material.emissiveMap = texture;\n }\n material.opacityMap = texture;\n material.alphaTest = 0.01;\n material.update();\n entity.render!.material = material;\n // Calculate aspect ratio\n const ratio = canvas.width / canvas.height;\n if (sx === 1 && sy === 1) {\n entity.setLocalScale(ratio, sz, 1);\n }\n else {\n entity.setLocalScale(sx, sz, sy);\n }\n // Compose face-forward correction with user rotation\n applyPlaneRotation(entity, rotXRad, rotYRad, rotZRad);\n // Mark texture as loaded\n (entity as ViewerRuntimeEntity).textureLoaded = true;\n (entity as ViewerRuntimeEntity).gifCanvas = canvas;\n (entity as ViewerRuntimeEntity).gifTexture = texture;\n (entity as ViewerRuntimeEntity).hotspotMaterial = material;\n // Enable entity if not hidden by visibility range\n if (!(entity as ViewerRuntimeEntity).visibilityRange || (entity as ViewerRuntimeEntity).shouldBeVisible) {\n entity.enabled = true;\n }\n (entity as ViewerRuntimeEntity).hiddenUntilTextureLoaded = false;\n // Try to load and animate the GIF frames using fetch + gif parsing\n fetch(gifUrl)\n .then(response => response.arrayBuffer())\n .then(buffer => {\n // Simple GIF frame extraction (will show animated if browser supports it via img tag)\n // For full animation support, we continuously redraw the img element\n let frameIndex = 0;\n const animateGif = () => {\n if (!entity.enabled || !(entity as ViewerRuntimeEntity).gifTexture)\n return;\n // Redraw from the img which browsers animate automatically\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.drawImage(img, 0, 0);\n (entity as ViewerRuntimeEntity).gifTexture?.upload();\n // Continue animation loop\n requestAnimationFrame(animateGif);\n };\n // Start animation loop\n (entity as ViewerRuntimeEntity).gifAnimationFrame = requestAnimationFrame(animateGif);\n })\n .catch(err => {\n console.warn('[Hotspot] GIF animation load failed, using static frame:', err);\n });\n console.log(`[Hotspot] Created GIF hotspot: ${hotspot.title}, opacity=${initialOpacity}, useLighting=${useLighting}`);\n };\n img.onerror = (err) => {\n console.error('[Hotspot] Failed to load GIF:', hotspot.gifUrl, err);\n // Fallback to sphere\n entity.addComponent('render', {\n type: 'sphere',\n castShadows: false,\n receiveShadows: false\n });\n const fallbackMaterial = new pc.StandardMaterial();\n fallbackMaterial.diffuse = parseColor(hotspot.color || '#FF00FF');\n fallbackMaterial.opacity = 0.8;\n fallbackMaterial.blendType = pc.BLEND_NORMAL;\n fallbackMaterial.update();\n entity.render!.material = fallbackMaterial;\n entity.setLocalScale(sx * 0.2, sy * 0.2, sz * 0.2);\n entity.enabled = true;\n (entity as ViewerRuntimeEntity).hiddenUntilTextureLoaded = false;\n };\n img.src = gifUrl;\n }\n catch (err) {\n console.error('[Hotspot] GIF hotspot creation failed:', err);\n }\n };\n loadAnimatedGif(hotspot.gifUrl);\n }\n else {\n // Default: sphere\n entity.addComponent('render', {\n type: 'sphere',\n castShadows: false,\n receiveShadows: false\n });\n const material = new pc.StandardMaterial();\n const color = parseColor(hotspot.color || '#4CAF50');\n material.diffuse = color;\n material.emissive = color.clone();\n (material.emissive as pc.Color).mulScalar(0.5);\n // Use additive blending for better GSplat compatibility\n const sphereOpacity = hotspot.opacity ?? 0.8;\n material.opacity = sphereOpacity;\n material.blendType = pc.BLEND_ADDITIVEALPHA;\n material.depthTest = true;\n material.depthWrite = true;\n material.update();\n entity.render!.material = material;\n entity.setLocalScale(sx * 0.2, sy * 0.2, sz * 0.2);\n }\n // Add collision for raycasting\n entity.addComponent('collision', {\n type: hotspot.type === 'sphere' ? 'sphere' : 'box',\n radius: 0.1,\n halfExtents: new pc.Vec3(0.5, 0.5, 0.05)\n });\n // Store hotspot data for click handling\n (entity as ViewerRuntimeEntity).hotspotData = hotspot as HotspotData;\n // Setup hotspot audio (spatial or non-spatial)\n const audioElements = setupHotspotAudio(entity, hotspot);\n if (audioElements) {\n (entity as ViewerRuntimeEntity).audioElements = audioElements;\n console.log(`[StorySplat Viewer] Audio setup for hotspot: ${hotspot.title || 'Untitled'}`);\n }\n // Billboard mode - make plane always face the camera (with optional range support)\n if (hotspot.billboard) {\n // Store original rotation for when billboard is disabled\n const originalRotation = entity.getEulerAngles().clone();\n // Check if billboard range is specified\n const hasBillboardRange = hotspot.billboardRangeStart !== undefined || hotspot.billboardRangeEnd !== undefined;\n // Initialize billboard active state\n (entity as ViewerRuntimeEntity)._billboardActive = !hasBillboardRange; // Active by default if no range\n (entity as ViewerRuntimeEntity)._billboardOriginalRotation = originalRotation;\n app.on('update', () => {\n // Only apply billboard if active (no range or within range)\n if ((entity as ViewerRuntimeEntity)._billboardActive) {\n entity.lookAt(camera.getPosition());\n // Rotate plane upright after lookAt\n entity.rotateLocal(90, 0, 0);\n }\n });\n }\n // Visibility range\n if (hotspot.visibilityRange) {\n (entity as ViewerRuntimeEntity).visibilityRange = hotspot.visibilityRange;\n entity.enabled = false; // Start hidden\n }\n app.root.addChild(entity);\n hotspotEntities.push(entity);\n console.log(`[StorySplat Viewer] Created hotspot: ${hotspot.title || 'Untitled'}`);\n return entity;\n }\n // Create all hotspots from config\n function createHotspots(): void {\n if (!config.hotspots || config.hotspots.length === 0) {\n console.log('[StorySplat Viewer] No hotspots to create');\n return;\n }\n console.log(`[StorySplat Viewer] Creating ${config.hotspots.length} hotspots...`);\n config.hotspots.forEach((hotspot, index: number) => {\n createSingleHotspot(hotspot, index);\n });\n }\n // Update hotspot visibility, opacity, and video playback based on progress\n function updateHotspotVisibility(): void {\n const scrollPercent = currentProgress * 100;\n const numWaypoints = config.waypoints?.length || 1;\n const waypointIndex = Math.round(currentProgress * Math.max(1, numWaypoints - 1));\n const cameraPos = camera.getPosition();\n hotspotEntities.forEach((entity) => {\n const hotspot = entity.hotspotData;\n if (!hotspot)\n return;\n // Determine if hotspot should be visible based on visibility range\n // alwaysVisible overrides visibility range\n let shouldBeVisible = true;\n if (hotspot.alwaysVisible) {\n shouldBeVisible = true;\n }\n else {\n const range = entity.visibilityRange;\n if (range) {\n if (range.type === 'waypoint') {\n shouldBeVisible = waypointIndex >= range.start && waypointIndex <= range.end;\n }\n else if (range.type === 'percentage') {\n shouldBeVisible = scrollPercent >= range.start && scrollPercent <= range.end;\n }\n }\n }\n // Store visibility state for texture load callback\n entity.shouldBeVisible = shouldBeVisible;\n // Update billboard state based on range (if billboard mode is enabled with range)\n if (hotspot.billboard && (hotspot.billboardRangeStart !== undefined || hotspot.billboardRangeEnd !== undefined)) {\n const rangeStart = hotspot.billboardRangeStart ?? 0;\n const rangeEnd = hotspot.billboardRangeEnd ?? 100;\n const billboardActive = scrollPercent >= rangeStart && scrollPercent <= rangeEnd;\n // Update billboard active state\n entity._billboardActive = billboardActive;\n // If billboard is deactivated and we have original rotation, restore it\n if (!billboardActive && entity._billboardOriginalRotation) {\n const origRot = entity._billboardOriginalRotation;\n entity.setEulerAngles(origRot.x, origRot.y, origRot.z);\n }\n }\n // Only enable if: should be visible AND (not an image OR texture is loaded)\n if (entity.hiddenUntilTextureLoaded) {\n // Image hotspot waiting for texture - keep hidden\n entity.enabled = false;\n }\n else {\n entity.enabled = shouldBeVisible;\n }\n // Handle video playback triggers\n if (entity.videoElement && hotspot.type === 'video') {\n const triggerMode = entity.mediaTriggerMode || 'click';\n // Proximity trigger: play when camera is within proximityDistance\n if (triggerMode === 'proximity') {\n const hotspotPos = entity.getPosition();\n const distance = cameraPos.distance(hotspotPos);\n const proximityDistance = entity.proximityDistance || 5;\n if (distance <= proximityDistance && !entity.isVideoPlaying) {\n // Enter proximity - play video\n playVideoHotspot(entity, hotspot);\n console.log(`[Hotspot] Proximity play: ${hotspot.title}, distance=${distance.toFixed(2)}`);\n }\n else if (distance > proximityDistance && entity.isVideoPlaying) {\n // Leave proximity - pause video\n pauseVideoHotspot(entity);\n console.log(`[Hotspot] Proximity pause: ${hotspot.title}, distance=${distance.toFixed(2)}`);\n }\n }\n // Scroll trigger: play when visible based on visibility range\n if (triggerMode === 'scroll') {\n if (shouldBeVisible && !entity.isVideoPlaying) {\n // Entered visibility range - play video\n playVideoHotspot(entity, hotspot);\n console.log(`[Hotspot] Scroll play: ${hotspot.title}, scroll=${scrollPercent.toFixed(1)}%`);\n }\n else if (!shouldBeVisible && entity.isVideoPlaying) {\n // Left visibility range - pause video\n pauseVideoHotspot(entity);\n console.log(`[Hotspot] Scroll pause: ${hotspot.title}, scroll=${scrollPercent.toFixed(1)}%`);\n }\n }\n }\n // Handle opacity animation (only if texture is loaded for image hotspots)\n if (hotspot.opacityMode === 'animated' && hotspot.opacityAnimation) {\n // Skip if this is an image hotspot and texture isn't loaded yet\n if (hotspot.type === 'image' && !entity.textureLoaded) {\n return;\n }\n const anim = hotspot.opacityAnimation;\n const startPercent = anim.startPercent ?? 0;\n const endPercent = anim.endPercent ?? 100;\n // Default to 1 (visible) if not explicitly set\n const startOpacity = anim.startOpacity !== undefined ? anim.startOpacity : 1;\n const endOpacity = anim.endOpacity !== undefined ? anim.endOpacity : 1;\n let opacity: number;\n if (scrollPercent <= startPercent) {\n opacity = startOpacity;\n }\n else if (scrollPercent >= endPercent) {\n opacity = endOpacity;\n }\n else {\n // Linear interpolation\n const animRange = endPercent - startPercent;\n const progress = (scrollPercent - startPercent) / animRange;\n opacity = startOpacity + (endOpacity - startOpacity) * progress;\n }\n opacity = Math.max(0, Math.min(1, opacity));\n // Update material opacity\n if (entity.hotspotMaterial) {\n entity.hotspotMaterial.opacity = opacity;\n entity.hotspotMaterial.update();\n }\n else if (entity.render && entity.render.material) {\n // Fallback to render material\n const material = entity.render.material as pc.Material & {\n opacity?: number;\n };\n material.opacity = opacity;\n material.update?.();\n }\n }\n });\n }\n // Helper to play/pause video (defined here so updateHotspotVisibility can use them)\n function playVideoHotspot(entity: ViewerRuntimeEntity, hotspot: HotspotConfigLike): void {\n const video = entity.videoElement as HTMLVideoElement;\n const alphaVideo = entity.alphaVideoElement as HTMLVideoElement | null;\n if (!video)\n return;\n // Set muted state based on hotspot settings (except for autoplay which is always muted initially)\n if (entity.mediaTriggerMode !== 'autoplay') {\n video.muted = hotspot.videoMuted !== false;\n }\n // Resume AudioContext for spatial audio (browser requires user interaction)\n if (entity.videoSpatialAudio && entity.videoSpatialAudio.audioCtx) {\n const audioCtx = entity.videoSpatialAudio.audioCtx as AudioContext;\n if (audioCtx.state === 'suspended') {\n audioCtx.resume().then(() => {\n console.log('[Audio] Video spatial audio context resumed');\n }).catch(err => console.warn('[Audio] Failed to resume video audio context:', err));\n }\n }\n video.play().catch(err => console.warn('Video play failed:', err));\n if (alphaVideo) {\n alphaVideo.play().catch(err => console.warn('Alpha video play failed:', err));\n }\n entity.isVideoPlaying = true;\n }\n function pauseVideoHotspot(entity: ViewerRuntimeEntity): void {\n const video = entity.videoElement as HTMLVideoElement;\n const alphaVideo = entity.alphaVideoElement as HTMLVideoElement | null;\n if (!video)\n return;\n video.pause();\n if (alphaVideo) {\n alphaVideo.pause();\n }\n entity.isVideoPlaying = false;\n }\n // Listen for progress updates to update hotspot visibility\n events.on('progressUpdate', () => {\n updateHotspotVisibility();\n });\n // Also call initially after hotspots are created\n setTimeout(() => {\n updateHotspotVisibility();\n }, 100);\n // =====================================================\n // PORTAL CREATION AND RENDERING\n // =====================================================\n // Create a single portal entity with full rendering (extracted for editor reuse)\n function createSinglePortal(portal: PortalConfigLike, index: number): pc.Entity {\n const entity = new pc.Entity(`portal-${index}`);\n // Get position (negate Z for BabylonJS -> PlayCanvas coordinate conversion)\n const pos = portal.position || { _x: 0, _y: 0, _z: 0 };\n entity.setPosition(pos._x ?? pos.x ?? 0, pos._y ?? pos.y ?? 0, -(pos._z ?? pos.z ?? 0));\n // Get scale\n const rawPortalScale = portal.scale || { _x: 1, _y: 1, _z: 1 };\n const portalScaleObj = typeof rawPortalScale === 'number' ? { x: rawPortalScale, y: rawPortalScale, z: rawPortalScale } as LegacyVec3 : rawPortalScale;\n const sx = Math.abs(portalScaleObj._x ?? portalScaleObj.x ?? 1);\n const sy = Math.abs(portalScaleObj._y ?? portalScaleObj.y ?? 1);\n const sz = portalScaleObj._z ?? portalScaleObj.z ?? 1;\n // Get rotation in radians (BabylonJS stores Euler angles in radians, YXZ order)\n const rot = portal.rotation || { _x: 0, _y: 0, _z: 0 };\n const rotXRad = rot._x ?? rot.x ?? 0;\n const rotYRad = rot._y ?? rot.y ?? 0;\n const rotZRad = rot._z ?? rot.z ?? 0;\n // Convert BJS rotation to PC quaternion (handles YXZ→RH conversion properly)\n entity.setRotation(bjsRotationToPC(rotXRad, rotYRad, rotZRad));\n // Create geometry based on type (similar to hotspots)\n if (portal.type === 'sphere') {\n entity.addComponent('render', {\n type: 'sphere',\n castShadows: false,\n receiveShadows: false\n });\n const material = new pc.StandardMaterial();\n const color = parseColor(portal.color || '#9C27B0'); // Purple default for portals\n material.diffuse = color;\n material.emissive = color.clone();\n (material.emissive as pc.Color).mulScalar(0.5);\n // For spheres: use full opacity with depth write for proper GSplat occlusion\n const sphereOpacity = portal.opacity ?? 0.8;\n if (sphereOpacity >= 0.95) {\n material.opacity = 1;\n material.blendType = pc.BLEND_NONE;\n material.depthTest = true;\n material.depthWrite = true;\n }\n else {\n material.opacity = sphereOpacity;\n material.blendType = pc.BLEND_ADDITIVEALPHA;\n material.depthTest = true;\n material.depthWrite = true;\n }\n material.update();\n entity.render!.material = material;\n entity.setLocalScale(sx * 0.2, sy * 0.2, sz * 0.2);\n }\n else if (portal.type === 'image' && portal.imageUrl) {\n entity.addComponent('render', {\n type: 'plane',\n castShadows: false,\n receiveShadows: false\n });\n const useLighting = portal.useLighting === true;\n const initialOpacity = portal.opacity ?? 1;\n (entity as ViewerRuntimeEntity).textureLoaded = false;\n (entity as ViewerRuntimeEntity).hiddenUntilTextureLoaded = true;\n entity.enabled = false;\n const material = createImageMaterial(portal.imageUrl, useLighting, initialOpacity, () => {\n (entity as ViewerRuntimeEntity).textureLoaded = true;\n if (!(entity as ViewerRuntimeEntity).visibilityRange || (entity as ViewerRuntimeEntity).shouldBeVisible) {\n entity.enabled = true;\n }\n (entity as ViewerRuntimeEntity).hiddenUntilTextureLoaded = false;\n });\n entity.render!.material = material;\n entity.setLocalScale(sx, sz, sy);\n applyPlaneRotation(entity, rotXRad, rotYRad, rotZRad);\n (entity as ViewerRuntimeEntity).portalMaterial = material;\n console.log(`[Portal] Created image portal: ${portal.title || portal.targetSceneName || 'Untitled'}`);\n }\n else if (portal.type === 'video' && portal.videoUrl) {\n // Video portal\n entity.addComponent('render', {\n type: 'plane',\n castShadows: false,\n receiveShadows: false\n });\n const video = document.createElement('video');\n video.src = portal.videoUrl;\n video.loop = true;\n video.muted = true;\n video.crossOrigin = 'anonymous';\n video.playsInline = true;\n video.autoplay = true;\n const videoTexture = new pc.Texture(app.graphicsDevice, {\n format: pc.PIXELFORMAT_R8_G8_B8,\n mipmaps: false,\n minFilter: pc.FILTER_LINEAR,\n magFilter: pc.FILTER_LINEAR,\n addressU: pc.ADDRESS_CLAMP_TO_EDGE,\n addressV: pc.ADDRESS_CLAMP_TO_EDGE\n });\n videoTexture.setSource(video);\n const material = new pc.StandardMaterial();\n material.useLighting = false;\n material.emissiveMap = videoTexture;\n material.emissive = new pc.Color(1, 1, 1);\n material.diffuse = new pc.Color(0, 0, 0);\n material.depthTest = true;\n material.depthWrite = true;\n material.cull = pc.CULLFACE_NONE;\n material.blendType = pc.BLEND_PREMULTIPLIED;\n material.opacity = portal.opacity ?? 1;\n material.update();\n entity.render!.material = material;\n entity.setLocalScale(sx, sz, sy);\n applyPlaneRotation(entity, rotXRad, rotYRad, rotZRad);\n // Update video texture every frame\n app.on('update', () => {\n if (video.readyState >= video.HAVE_CURRENT_DATA) {\n videoTexture.setSource(video);\n }\n });\n // Start playing when visible\n video.play().catch(e => console.log('[Portal] Video autoplay blocked:', e));\n (entity as ViewerRuntimeEntity).videoElement = video;\n (entity as ViewerRuntimeEntity).portalMaterial = material;\n console.log(`[Portal] Created video portal: ${portal.title || portal.targetSceneName || 'Untitled'}`);\n }\n else if (portal.type === 'gif' && portal.gifUrl) {\n // GIF portal - use AnimatedGifTexture if available, otherwise static image\n entity.addComponent('render', {\n type: 'plane',\n castShadows: false,\n receiveShadows: false\n });\n const useLighting = portal.useLighting === true;\n const initialOpacity = portal.opacity ?? 1;\n // Try to use animated GIF texture, fallback to static image\n const material = createImageMaterial(portal.gifUrl, useLighting, initialOpacity, () => {\n (entity as ViewerRuntimeEntity).textureLoaded = true;\n if (!(entity as ViewerRuntimeEntity).visibilityRange || (entity as ViewerRuntimeEntity).shouldBeVisible) {\n entity.enabled = true;\n }\n (entity as ViewerRuntimeEntity).hiddenUntilTextureLoaded = false;\n });\n entity.render!.material = material;\n entity.setLocalScale(sx, sz, sy);\n applyPlaneRotation(entity, rotXRad, rotYRad, rotZRad);\n (entity as ViewerRuntimeEntity).textureLoaded = false;\n (entity as ViewerRuntimeEntity).hiddenUntilTextureLoaded = true;\n entity.enabled = false;\n (entity as ViewerRuntimeEntity).portalMaterial = material;\n console.log(`[Portal] Created GIF portal: ${portal.title || portal.targetSceneName || 'Untitled'}`);\n }\n else {\n // Default: sphere\n entity.addComponent('render', {\n type: 'sphere',\n castShadows: false,\n receiveShadows: false\n });\n const material = new pc.StandardMaterial();\n const color = parseColor(portal.color || '#9C27B0');\n material.diffuse = color;\n material.emissive = color.clone();\n (material.emissive as pc.Color).mulScalar(0.5);\n // Use additive blending for better GSplat compatibility\n const sphereOpacity = portal.opacity ?? 0.8;\n material.opacity = sphereOpacity;\n material.blendType = pc.BLEND_ADDITIVEALPHA;\n material.depthTest = true;\n material.depthWrite = true;\n material.update();\n entity.render!.material = material;\n entity.setLocalScale(sx * 0.2, sy * 0.2, sz * 0.2);\n }\n // Add collision for raycasting\n entity.addComponent('collision', {\n type: portal.type === 'sphere' ? 'sphere' : 'box',\n radius: 0.1,\n halfExtents: new pc.Vec3(0.5, 0.5, 0.05)\n });\n // Store portal data for click handling\n (entity as ViewerRuntimeEntity).portalData = portal as PortalData;\n // Billboard mode\n if (portal.billboard) {\n app.on('update', () => {\n if (entity.enabled) {\n entity.lookAt(camera.getPosition());\n entity.rotateLocal(90, 0, 0);\n }\n });\n }\n // Visibility range\n if (portal.visibilityRange) {\n (entity as ViewerRuntimeEntity).visibilityRange = portal.visibilityRange;\n entity.enabled = false;\n }\n app.root.addChild(entity);\n portalEntities.push(entity);\n console.log(`[StorySplat Viewer] Created portal: ${portal.title || portal.targetSceneName || 'Untitled'} -> ${portal.targetSceneId}`);\n return entity;\n }\n // Create all portals (similar to hotspots but for scene-to-scene navigation)\n function createPortals(): void {\n if (!config.portals || config.portals.length === 0) {\n console.log('[StorySplat Viewer] No portals to create');\n return;\n }\n console.log(`[StorySplat Viewer] Creating ${config.portals.length} portals...`);\n config.portals.forEach((portal, index: number) => {\n createSinglePortal(portal, index);\n });\n }\n // Update portal visibility based on progress\n function updatePortalVisibility(): void {\n const scrollPercent = currentProgress * 100;\n const numWaypoints = config.waypoints?.length || 1;\n const waypointIndex = Math.round(currentProgress * Math.max(1, numWaypoints - 1));\n const cameraPos = camera.getPosition();\n portalEntities.forEach((entity) => {\n const portal = entity.portalData;\n if (!portal)\n return;\n // Determine if portal should be visible based on visibility range\n let shouldBeVisible = true;\n const range = entity.visibilityRange;\n if (range) {\n if (range.type === 'waypoint') {\n shouldBeVisible = waypointIndex >= range.start && waypointIndex <= range.end;\n }\n else if (range.type === 'percentage') {\n shouldBeVisible = scrollPercent >= range.start && scrollPercent <= range.end;\n }\n }\n entity.shouldBeVisible = shouldBeVisible;\n // Enable/disable based on visibility and texture loaded state\n if (entity.hiddenUntilTextureLoaded) {\n entity.enabled = false;\n }\n else {\n entity.enabled = shouldBeVisible;\n }\n // Proximity activation check\n if (portal.activationMode === 'proximity' && shouldBeVisible) {\n const portalPos = entity.getPosition();\n const distance = cameraPos.distance(portalPos);\n const proximityDistance = portal.proximityDistance || 2;\n if (distance <= proximityDistance && !entity.proximityTriggered) {\n entity.proximityTriggered = true;\n console.log(`[Portal] Proximity triggered: ${portal.title || portal.targetSceneName}, navigating to scene ${portal.targetSceneId}`);\n handlePortalNavigation(portal);\n }\n else if (distance > proximityDistance) {\n entity.proximityTriggered = false;\n }\n }\n });\n }\n // Listen for progress updates to update portal visibility\n events.on('progressUpdate', () => {\n updatePortalVisibility();\n });\n // Also call initially after portals are created\n setTimeout(() => {\n updatePortalVisibility();\n }, 100);\n // Raycast helper for portal detection\n function raycastPortals(x: number, y: number): {\n entity: ViewerRuntimeEntity;\n portal: PortalConfigLike;\n } | null {\n const from = camera.camera!.screenToWorld(x, y, camera.camera!.nearClip);\n const to = camera.camera!.screenToWorld(x, y, camera.camera!.farClip);\n let closestHit: {\n entity: ViewerRuntimeEntity;\n distance: number;\n } | null = null;\n portalEntities.forEach(entity => {\n if (!entity.enabled)\n return;\n const pos = entity.getPosition();\n const dir = new pc.Vec3().sub2(to, from).normalize();\n const toEntity = new pc.Vec3().sub2(pos, from);\n const t = toEntity.dot(dir);\n if (t < 0)\n return;\n const closestPoint = new pc.Vec3().add2(from, dir.clone().mulScalar(t));\n const distance = closestPoint.distance(pos);\n const entityScale = entity.getLocalScale();\n const hitRadius = Math.max(entityScale.x, entityScale.y, 0.3) * 0.6;\n if (distance < hitRadius) {\n if (!closestHit || t < closestHit.distance) {\n closestHit = { entity, distance: t };\n }\n }\n });\n // TypeScript doesn't track assignments inside forEach callbacks\n const portalHitResult = closestHit as { entity: ViewerRuntimeEntity; distance: number } | null;\n if (portalHitResult !== null) {\n const entity = portalHitResult.entity;\n return { entity, portal: entity.portalData! as PortalConfigLike };\n }\n return null;\n }\n // Handle portal navigation (scene-to-scene)\n async function handlePortalNavigation(portal: PortalConfigLike): Promise<void> {\n if (!portal.targetSceneId) {\n console.warn('[Portal] No target scene ID specified');\n return;\n }\n console.log(`[StorySplat Viewer] Portal activated: navigating to scene ${portal.targetSceneId}`);\n // Emit event for external handlers\n events.emit('portalActivated', {\n portalId: portal.id,\n targetSceneId: portal.targetSceneId,\n targetSceneName: portal.targetSceneName\n });\n // Show loading UI - use pretty name if available, otherwise generic text\n showPortalLoadingUI(container, portal.targetSceneName || portal.title || 'scene');\n try {\n // Fetch the new scene data\n const sceneApiUrl = `https://discover.storysplat.com/api/scene/${portal.targetSceneId}`;\n console.log(`[Portal] Fetching scene from: ${sceneApiUrl}`);\n const response = await fetch(sceneApiUrl);\n if (!response.ok) {\n throw new Error(`Failed to fetch scene: ${response.status} ${response.statusText}`);\n }\n const result = await response.json();\n const newSceneData = result.data || result;\n // Update loading UI with the actual scene name from the API\n if (newSceneData.name) {\n updatePortalLoadingText(container, newSceneData.name);\n }\n // Store current instance reference before cleanup\n const currentInstance = instance;\n // Cleanup current scene (iOS memory management)\n cleanupForPortalNavigation();\n // Small delay for garbage collection (iOS)\n await new Promise(resolve => setTimeout(resolve, 100));\n // Create new viewer with the fetched scene data\n // Note: We're recreating in the same container, so we need to remove the current canvas first\n if (canvas && canvas.parentNode) {\n canvas.remove();\n }\n // Create new viewer (this will replace 'instance' reference for external code)\n const newViewer = await createViewer(container, newSceneData, {});\n // Hide loading UI\n hidePortalLoadingUI(container);\n console.log(`[Portal] Successfully navigated to scene: ${portal.targetSceneId}`);\n // Return the new viewer instance (for programmatic use)\n return;\n }\n catch (error) {\n console.error('[Portal] Navigation failed:', error);\n hidePortalLoadingUI(container);\n // Show error message\n const errorDiv = document.createElement('div');\n errorDiv.className = 'storysplat-portal-error';\n errorDiv.textContent = `Failed to load scene: ${(error as Error).message}`;\n Object.assign(errorDiv.style, {\n // Keep errors contained to the viewer container (important for embeds)\n position: 'absolute',\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n background: 'rgba(0,0,0,0.9)',\n color: '#ff6b6b',\n padding: '20px',\n borderRadius: '8px',\n zIndex: '100004',\n fontFamily: 'system-ui, sans-serif'\n });\n container.appendChild(errorDiv);\n setTimeout(() => errorDiv.remove(), 3000);\n }\n }\n // Cleanup for portal navigation (memory management)\n function cleanupForPortalNavigation(): void {\n console.log('[Portal] Cleaning up current scene for navigation...');\n // Stop playback\n pause();\n // Stop all hotspot videos\n hotspotEntities.forEach((entity) => {\n if (entity.videoElement) {\n entity.videoElement.pause();\n entity.videoElement.src = '';\n }\n if (entity.alphaVideoElement) {\n entity.alphaVideoElement.pause();\n entity.alphaVideoElement.src = '';\n }\n });\n // Cleanup animated GIFs\n animatedGifs.forEach(gif => gif.destroy());\n animatedGifs.length = 0;\n // Cleanup custom meshes\n cleanupCustomMeshes();\n // Destroy hotspot entities\n hotspotEntities.forEach(entity => {\n entity.destroy();\n });\n hotspotEntities.length = 0;\n // Destroy portal entities\n portalEntities.forEach(entity => {\n entity.destroy();\n });\n portalEntities.length = 0;\n // Destroy splat entity\n if (splatEntity) {\n splatEntity.destroy();\n splatEntity = null;\n }\n // Cleanup HTML Mesh Manager\n const portalHtmlMgr = (app as ViewerRuntimeApp).__htmlMeshManager;\n if (portalHtmlMgr) {\n portalHtmlMgr.destroy();\n }\n // Cleanup Custom Script System\n const portalScriptSys = (app as ViewerRuntimeApp).__customScriptSystem;\n if (portalScriptSys) {\n portalScriptSys.dispose();\n }\n console.log('[Portal] Cleanup complete');\n }\n // Show portal loading UI\n function showPortalLoadingUI(container: HTMLElement, sceneName: string): void {\n // Remove any existing loading UI\n const existing = container.querySelector('.storysplat-portal-loading');\n if (existing)\n existing.remove();\n // Ensure the overlay is positioned relative to the viewer container\n try {\n const pos = window.getComputedStyle(container).position;\n if (pos === 'static')\n container.style.position = 'relative';\n }\n catch {\n // Best-effort only\n }\n const loadingDiv = document.createElement('div');\n loadingDiv.className = 'storysplat-portal-loading';\n const spinner = document.createElement('div');\n spinner.className = 'storysplat-portal-spinner';\n const text = document.createElement('div');\n text.className = 'storysplat-portal-loading-text';\n text.textContent = getButtonLabel(currentButtonLabels, 'loadingScene').replace('{name}', sceneName);\n loadingDiv.appendChild(spinner);\n loadingDiv.appendChild(text);\n // Styles - use scene's uiColor for the spinner\n Object.assign(loadingDiv.style, {\n position: 'absolute',\n inset: '0',\n background: 'rgba(0, 0, 0, 0.85)',\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: '100003',\n fontFamily: 'system-ui, sans-serif'\n });\n Object.assign(spinner.style, {\n width: '50px',\n height: '50px',\n border: '4px solid rgba(255, 255, 255, 0.2)',\n borderTop: `4px solid ${uiColor}`,\n borderRadius: '50%',\n animation: 'storysplat-portal-spin 1s linear infinite',\n marginBottom: '20px'\n });\n Object.assign(text.style, {\n color: '#ffffff',\n fontSize: '18px'\n });\n // Add keyframes for spinner animation\n if (!document.getElementById('storysplat-portal-styles')) {\n const styleEl = document.createElement('style');\n styleEl.id = 'storysplat-portal-styles';\n styleEl.textContent = `\n @keyframes storysplat-portal-spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n `;\n document.head.appendChild(styleEl);\n }\n container.appendChild(loadingDiv);\n }\n // Update portal loading text with actual scene name (after API fetch)\n function updatePortalLoadingText(container: HTMLElement, sceneName: string): void {\n const textEl = container.querySelector('.storysplat-portal-loading-text');\n if (textEl) {\n textEl.textContent = getButtonLabel(currentButtonLabels, 'loadingScene').replace('{name}', sceneName);\n }\n }\n // Hide portal loading UI\n function hidePortalLoadingUI(container: HTMLElement): void {\n const loadingDiv = container.querySelector('.storysplat-portal-loading');\n if (loadingDiv) {\n loadingDiv.remove();\n }\n }\n // Raycast helper for hotspot detection\n const canvasEl = app.graphicsDevice.canvas as HTMLCanvasElement;\n // Suppress clicks when the user is dragging to look around\n let clickSuppressed = false;\n let pointerDown = false;\n let pointerStartX = 0;\n let pointerStartY = 0;\n const dragThresholdPx = 5;\n canvasEl.addEventListener('pointerdown', (e: PointerEvent) => {\n if (e.button !== 0)\n return;\n pointerDown = true;\n clickSuppressed = false;\n pointerStartX = e.clientX;\n pointerStartY = e.clientY;\n });\n canvasEl.addEventListener('pointermove', (e: PointerEvent) => {\n if (!pointerDown)\n return;\n const dx = e.clientX - pointerStartX;\n const dy = e.clientY - pointerStartY;\n if ((dx * dx + dy * dy) > (dragThresholdPx * dragThresholdPx)) {\n clickSuppressed = true;\n }\n });\n const clearPointerDown = () => {\n pointerDown = false;\n };\n canvasEl.addEventListener('pointerup', clearPointerDown);\n canvasEl.addEventListener('pointercancel', clearPointerDown);\n function raycastHotspots(x: number, y: number): {\n entity: ViewerRuntimeEntity;\n hotspot: HotspotConfigLike;\n } | null {\n const from = camera.camera!.screenToWorld(x, y, camera.camera!.nearClip);\n const to = camera.camera!.screenToWorld(x, y, camera.camera!.farClip);\n let closestHit: {\n entity: ViewerRuntimeEntity;\n distance: number;\n } | null = null;\n hotspotEntities.forEach(entity => {\n if (!entity.enabled)\n return;\n const pos = entity.getPosition();\n const dir = new pc.Vec3().sub2(to, from).normalize();\n const toEntity = new pc.Vec3().sub2(pos, from);\n const t = toEntity.dot(dir);\n if (t < 0)\n return;\n const closestPoint = new pc.Vec3().add2(from, dir.clone().mulScalar(t));\n const distance = closestPoint.distance(pos);\n // Use the entity's actual scale to determine hit radius\n // Take the maximum of X and Y scale for a reasonable hit area\n const entityScale = entity.getLocalScale();\n const hitRadius = Math.max(entityScale.x, entityScale.y, 0.3) * 0.6; // Min 0.18 units, scaled by 0.6\n if (distance < hitRadius) {\n if (!closestHit || t < closestHit.distance) {\n closestHit = { entity, distance: t };\n }\n }\n });\n // TypeScript doesn't track assignments inside forEach callbacks\n const hotspotHitResult = closestHit as { entity: ViewerRuntimeEntity; distance: number } | null;\n if (hotspotHitResult !== null) {\n const entity = hotspotHitResult.entity;\n return { entity, hotspot: entity.hotspotData! as HotspotConfigLike };\n }\n return null;\n }\n // Hover detection for cursor change and hover popups\n let currentHoverHotspot: HotspotConfigLike | null = null;\n let isMouseOverPopup = false;\n // Track when mouse is over the popup\n const popup = container.querySelector('.storysplat-hotspot-popup') as HTMLElement;\n const overlay = container.querySelector('.storysplat-hotspot-overlay') as HTMLElement;\n if (popup) {\n popup.addEventListener('mouseenter', () => {\n isMouseOverPopup = true;\n });\n popup.addEventListener('mouseleave', () => {\n isMouseOverPopup = false;\n // Close popup when mouse leaves the popup (for hover-activated hotspots)\n if (currentHoverHotspot && currentHoverHotspot.activationMode === 'hover') {\n popup.classList.remove('visible');\n if (overlay)\n overlay.classList.remove('visible');\n currentHoverHotspot = null;\n }\n });\n }\n canvasEl.addEventListener('mousemove', (e: MouseEvent) => {\n const rect = canvasEl.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n // Check portals first\n const portalHit = raycastPortals(x, y);\n if (portalHit && portalHit.portal) {\n const portal = portalHit.portal;\n // Show pointer cursor for click-activated portals\n const effectiveActivationMode = portal.activationMode || 'click';\n if (effectiveActivationMode === 'click') {\n canvasEl.style.cursor = 'pointer';\n }\n else {\n canvasEl.style.cursor = 'default';\n }\n return; // Portals take precedence over hotspots\n }\n const hit = raycastHotspots(x, y);\n if (hit && hit.hotspot) {\n const hotspot = hit.hotspot;\n // Show pointer cursor for interactive hotspots\n if (hotspot.activationMode === 'click' || hotspot.activationMode === 'hover' || hotspot.type === 'video') {\n canvasEl.style.cursor = 'pointer';\n }\n else {\n canvasEl.style.cursor = 'default';\n }\n // Show popup on hover for hover-activated hotspots\n if (hotspot.activationMode === 'hover' && currentHoverHotspot !== hotspot) {\n currentHoverHotspot = hotspot;\n if (hotspot.information || hotspot.photoUrl || hotspot.iframeUrl || hotspot.externalLinkUrl) {\n showHotspotPopup(container, hotspot as HotspotData, currentButtonLabels);\n }\n }\n }\n else {\n canvasEl.style.cursor = 'default';\n // Only hide popup if mouse is NOT over the popup element\n if (currentHoverHotspot && currentHoverHotspot.activationMode === 'hover' && !isMouseOverPopup) {\n const popupEl = container.querySelector('.storysplat-hotspot-popup') as HTMLElement;\n const overlayEl = container.querySelector('.storysplat-hotspot-overlay') as HTMLElement;\n if (popupEl)\n popupEl.classList.remove('visible');\n if (overlayEl)\n overlayEl.classList.remove('visible');\n currentHoverHotspot = null;\n }\n }\n });\n // Click handling for hotspots and portals\n canvasEl.addEventListener('click', (e: MouseEvent) => {\n if (clickSuppressed) {\n clickSuppressed = false;\n return;\n }\n const rect = canvasEl.getBoundingClientRect();\n const x = e.clientX - rect.left;\n const y = e.clientY - rect.top;\n // Check for portal clicks first (portals take precedence)\n const portalHit = raycastPortals(x, y);\n if (portalHit !== null && portalHit.portal) {\n const portal = portalHit.portal;\n console.log('[StorySplat Viewer] Portal clicked:', portal.title || portal.targetSceneName);\n // Handle click-activated portals\n const effectiveActivationMode = portal.activationMode || 'click';\n if (effectiveActivationMode === 'click') {\n const shouldConfirm = portal.confirmNavigation !== false\n && (portal.title || portal.targetSceneName || portal.targetSceneId);\n if (shouldConfirm && portalPopupEl) {\n showPortalPopup(portal as PortalData);\n }\n else {\n handlePortalNavigation(portal);\n }\n }\n return; // Don't process hotspot clicks when portal is clicked\n }\n const hitResult = raycastHotspots(x, y);\n if (hitResult !== null) {\n const entity = hitResult.entity;\n const hotspot = hitResult.hotspot;\n console.log('[StorySplat Viewer] Hotspot clicked:', hotspot.title);\n // Handle hotspot audio play/pause (spatial or non-spatial)\n if (entity.audioElements && entity.audioElements.audio) {\n const audioElements = entity.audioElements as HotspotAudioElements;\n const audio = audioElements.audio;\n if (audio.paused) {\n // Resume AudioContext on user interaction (browser requirement)\n if (audioElements.audioCtx && audioElements.audioCtx.state === 'suspended') {\n audioElements.audioCtx.resume();\n }\n audio.play().catch(err => console.error('[Audio] Hotspot audio play failed:', err));\n console.log('[Audio] Hotspot audio started:', hotspot.title);\n }\n else {\n audio.pause();\n console.log('[Audio] Hotspot audio paused:', hotspot.title);\n }\n }\n // Handle video toggle (for click or autoplay mode - autoplay needs click to unmute)\n if (entity.videoElement && (entity.mediaTriggerMode === 'click' || entity.mediaTriggerMode === 'autoplay')) {\n const video = entity.videoElement as HTMLVideoElement;\n const alphaVideo = entity.alphaVideoElement as HTMLVideoElement | null;\n if (entity.mediaTriggerMode === 'autoplay' && !video.paused && video.muted) {\n // Autoplay mode: click unmutes if already playing\n video.muted = hotspot.videoMuted === false ? false : false; // Unmute on click\n console.log('[Hotspot] Video unmuted by click');\n }\n else if (video.paused) {\n // Play video\n playVideoHotspot(entity, hotspot);\n console.log('[Hotspot] Video started');\n }\n else {\n // Pause video\n pauseVideoHotspot(entity);\n console.log('[Hotspot] Video paused');\n }\n }\n // Show popup for click-activated hotspots with content\n // Default to 'click' if activationMode is not set\n const effectiveActivationMode = hotspot.activationMode || 'click';\n // Show popup if there's any displayable content (title, information, photo, iframe, or link)\n const hasPopupContent = hotspot.title || hotspot.information || hotspot.photoUrl || hotspot.iframeUrl || hotspot.externalLinkUrl;\n if (effectiveActivationMode === 'click' && hasPopupContent) {\n // In XR mode, show AR content plane instead of DOM popup\n if (isInXR) {\n if (arContentVisible) {\n // If already showing AR content, hide it (toggle behavior)\n hideARContent();\n }\n else {\n showARContent(hotspot as HotspotData);\n }\n }\n else {\n showHotspotPopup(container, hotspot as HotspotData, currentButtonLabels);\n }\n }\n // Handle hotspot teleportation (teleport to waypoint or percentage)\n const teleportWaypoint = hotspot.teleportWaypoint ?? hotspot.teleportToWaypoint;\n const teleportPercent = hotspot.teleportPercent ?? hotspot.teleportToPercent;\n const teleportMode = hotspot.teleportMode || 'animate'; // Default to animate for smooth experience\n let calculatedTargetProgress: number | null = null;\n if (teleportWaypoint !== undefined && teleportWaypoint !== -1) {\n // Teleport to specific waypoint\n const numWaypoints = config.waypoints?.length || 1;\n const targetIndex = Math.max(0, Math.min(teleportWaypoint, numWaypoints - 1));\n calculatedTargetProgress = numWaypoints > 1 ? targetIndex / (numWaypoints - 1) : 0;\n console.log('[Hotspot] Teleporting to waypoint:', targetIndex, '(progress:', calculatedTargetProgress, ', mode:', teleportMode, ')');\n }\n else if (teleportPercent !== undefined && teleportPercent !== -1) {\n // Teleport to specific percentage (0-100)\n calculatedTargetProgress = Math.max(0, Math.min(teleportPercent / 100, 1));\n console.log('[Hotspot] Teleporting to percent:', teleportPercent, '(progress:', calculatedTargetProgress, ', mode:', teleportMode, ')');\n }\n if (calculatedTargetProgress !== null) {\n if (teleportMode === 'instant') {\n // Instant jump - directly set position\n currentProgress = calculatedTargetProgress;\n targetProgress = calculatedTargetProgress; // Keep inertia system in sync\n updateCameraFromProgress(currentProgress);\n }\n else {\n // Animate (default) - smooth transition\n targetProgress = calculatedTargetProgress; // Keep inertia system in sync\n animateToProgress(calculatedTargetProgress, 800); // 800ms for smooth transition\n }\n }\n }\n });\n // Double-click/double-tap to focus camera on clicked point (explore mode only)\n async function handleDoubleClickFocus(screenX: number, screenY: number): Promise<void> {\n if (currentCameraMode !== 'explore')\n return;\n console.log('[StorySplat Viewer] Double-click focus at:', screenX, screenY);\n // First check if we hit a hotspot\n const hotspotHit = raycastHotspots(screenX, screenY);\n if (hotspotHit) {\n const pos = hotspotHit.entity.getPosition();\n console.log('[StorySplat Viewer] Focusing on hotspot at:', pos.x, pos.y, pos.z);\n if (cameraControls.mode === 'fly') {\n cameraControls.flyTo(pos);\n } else {\n cameraControls.focus(pos, false);\n }\n return;\n }\n // Try to pick the GSplat using pc.Picker with depth buffer (PlayCanvas 2.14+)\n try {\n // Use 25% scale for performance (matching PlayCanvas official picking example)\n const pickerScale = 0.25;\n picker.resize(Math.floor(canvasEl.clientWidth * pickerScale), Math.floor(canvasEl.clientHeight * pickerScale));\n const worldLayer = app.scene.layers.getLayerByName('World');\n if (!worldLayer) {\n console.warn('[StorySplat Viewer] World layer not found');\n return;\n }\n picker.prepare(camera.camera!, app.scene, [worldLayer]);\n // Calculate scaled pick coordinates\n const scaledX = Math.floor(screenX * pickerScale);\n const scaledY = Math.floor(screenY * pickerScale);\n // Use getWorldPointAsync to get the actual 3D intersection point directly\n // This is the proper way to pick splats with depth-enabled picker\n const worldPoint = await picker.getWorldPointAsync(scaledX, scaledY);\n if (worldPoint) {\n // Validate the point is reasonable (not at infinity or behind camera)\n const cameraPos = camera.getPosition();\n const distance = cameraPos.distance(worldPoint);\n if (distance > 0.1 && distance < 1000) {\n console.log('[StorySplat Viewer] Focusing on GSplat at:', worldPoint.x.toFixed(2), worldPoint.y.toFixed(2), worldPoint.z.toFixed(2), 'distance:', distance.toFixed(2));\n if (cameraControls.mode === 'fly') {\n cameraControls.flyTo(worldPoint);\n } else {\n cameraControls.focus(worldPoint, false);\n }\n return;\n }\n }\n // Fallback: try mesh instance selection if world point failed\n const meshInstances = await picker.getSelectionAsync(scaledX, scaledY, 1, 1);\n if (meshInstances.length > 0) {\n const mi = meshInstances[0] as pc.MeshInstance;\n // Use AABB center as a rough focus point (only available on MeshInstance)\n if (!mi.aabb) {\n console.log('[StorySplat Viewer] No AABB available for picked object');\n return;\n }\n const aabbCenter = mi.aabb.center.clone();\n console.log('[StorySplat Viewer] Focusing on mesh AABB center:', aabbCenter.x.toFixed(2), aabbCenter.y.toFixed(2), aabbCenter.z.toFixed(2));\n if (cameraControls.mode === 'fly') {\n cameraControls.flyTo(aabbCenter);\n } else {\n cameraControls.focus(aabbCenter, false);\n }\n }\n else {\n console.log('[StorySplat Viewer] No pick result at click point');\n }\n }\n catch (err) {\n console.warn('[StorySplat Viewer] Picking failed:', err);\n }\n }\n // Desktop double-click handler\n canvasEl.addEventListener('dblclick', (e: MouseEvent) => {\n const rect = canvasEl.getBoundingClientRect();\n handleDoubleClickFocus(e.clientX - rect.left, e.clientY - rect.top);\n });\n // Mobile double-tap detection\n let lastTapTime = 0;\n const DOUBLE_TAP_THRESHOLD = 300;\n canvasEl.addEventListener('touchend', (e: TouchEvent) => {\n if (e.changedTouches.length !== 1)\n return;\n const now = Date.now();\n if (now - lastTapTime < DOUBLE_TAP_THRESHOLD) {\n const touch = e.changedTouches[0];\n const rect = canvasEl.getBoundingClientRect();\n handleDoubleClickFocus(touch.clientX - rect.left, touch.clientY - rect.top);\n lastTapTime = 0;\n }\n else {\n lastTapTime = now;\n }\n });\n // Load the splat or frame sequence after app starts\n updateProgress(0.2, 'Initializing...');\n // Choose loader based on scene data\n const hasFrameSequence = scene.frameSequence && scene.frameSequence.frameUrls && scene.frameSequence.frameUrls.length > 0;\n const loadContent = hasFrameSequence ? loadFrameSequence : loadSplat;\n loadContent().then(() => {\n updateProgress(1.0, 'Ready!');\n // Create hotspots after splat is loaded\n createHotspots();\n // Create portals for scene-to-scene navigation\n createPortals();\n // Setup waypoint audio (audio interactions on waypoints)\n setupWaypointAudio();\n // Setup standalone audio emitters\n setupAudioEmitters();\n // Initialize particle systems\n console.log('[StorySplat Viewer] 🎆 Calling initParticleSystems...');\n initParticleSystems();\n // Initialize custom meshes (3D models with animations, audio)\n initCustomMeshes();\n // Initialize skybox\n initSkybox();\n // Initialize custom lights\n initCustomLights();\n // Start reveal effect IMMEDIATELY (before preloader hides)\n // The preloader is semi-transparent (0.85 opacity) so users can see the reveal through it\n if (revealScript) {\n revealScript.enabled = true;\n console.log('[StorySplat Viewer] Reveal effect started immediately');\n }\n // Hide preloader AFTER a short delay to let reveal effect start\n // This ensures users see the beginning of the reveal animation through the semi-transparent preloader\n setTimeout(() => {\n if (uiElements.preloader) {\n hidePreloader(uiElements.preloader);\n }\n }, 200);\n // Setup XR (VR/AR) if enabled\n setupXR();\n // Setup HTML Meshes if configured\n if (config.htmlMeshes && config.htmlMeshes.length > 0) {\n console.log('[StorySplat Viewer] Setting up HTML meshes:', config.htmlMeshes.length);\n const htmlMeshManager = setupHtmlMeshes(app, config.htmlMeshes);\n // Store manager reference for cleanup\n (app as ViewerRuntimeApp).__htmlMeshManager = htmlMeshManager;\n // Listen for progress updates to update HTML mesh visibility\n events.on('progressUpdate', () => {\n const scrollPercent = currentProgress * 100;\n const numWaypoints = config.waypoints?.length || 1;\n const waypointIndex = Math.round(currentProgress * Math.max(1, numWaypoints - 1));\n htmlMeshManager.updateVisibility(scrollPercent, waypointIndex);\n });\n }\n // Setup Custom Script System\n if (config.customScript && config.customScript.trim() !== '') {\n console.log('[StorySplat Viewer] Initializing custom script system...');\n const customScriptSystem = setupCustomScript(app, camera, canvas, config.customScript, () => currentProgress, () => currentWaypointIndex, () => hotspotEntities, () => splatEntity ? [splatEntity] : [], () => config.htmlMeshes || []);\n if (customScriptSystem) {\n // Store for cleanup\n (app as ViewerRuntimeApp).__customScriptSystem = customScriptSystem;\n console.log('[StorySplat Viewer] Custom script system initialized');\n }\n }\n // Check for URL parameters to support deep-linking\n // ?waypoint=2 will navigate to waypoint index 2\n // ?autoplay=true will start autoplay\n try {\n const urlParams = new URLSearchParams(window.location.search);\n const waypointParam = urlParams.get('waypoint');\n const autoplayParam = urlParams.get('autoplay');\n if (waypointParam !== null && config.waypoints && config.waypoints.length > 0) {\n const targetIndex = parseInt(waypointParam, 10);\n if (!isNaN(targetIndex) && targetIndex >= 0 && targetIndex < config.waypoints.length) {\n console.log('[StorySplat Viewer] URL param: navigating to waypoint', targetIndex);\n goToWaypoint(targetIndex);\n }\n }\n if (autoplayParam === 'true' && !options.autoPlay && !config.autoPlay) {\n console.log('[StorySplat Viewer] URL param: starting autoplay');\n play();\n }\n }\n catch (e) {\n // URLSearchParams may not be available in some environments\n console.warn('[StorySplat Viewer] Could not parse URL parameters:', e);\n }\n events.emit('ready');\n console.log('[StorySplat Viewer] Ready');\n // Apply the default camera mode now that the scene is loaded.\n // This ensures explore mode properly initializes CameraControls with\n // syncFromPose to the first waypoint position. Without this, loading\n // directly into explore mode would leave CameraControls uninitialized.\n setCameraMode(defaultMode);\n // Connect UI to viewer controls\n if (showUI) {\n connectUIToViewer(uiElements, {\n nextWaypoint,\n prevWaypoint,\n play,\n pause,\n isPlaying: () => isPlaying,\n getCurrentWaypointIndex: () => currentWaypointIndex,\n getWaypointCount: () => config.waypoints?.length || 0,\n getWaypoints: () => config.waypoints || [],\n setCameraMode,\n on: (event, callback) => events.on(event, callback)\n }, defaultMode, currentButtonLabels);\n // Wire up mute button\n if (uiElements.muteButton) {\n uiElements.muteButton.addEventListener('click', () => {\n const muted = toggleMuteAll();\n const unmutedIcon = uiElements.muteButton!.querySelector('.storysplat-unmuted-icon') as HTMLElement;\n const mutedIcon = uiElements.muteButton!.querySelector('.storysplat-muted-icon') as HTMLElement;\n if (unmutedIcon) unmutedIcon.style.display = muted ? 'none' : '';\n if (mutedIcon) mutedIcon.style.display = muted ? '' : 'none';\n uiElements.muteButton!.setAttribute('aria-label', muted\n ? getButtonLabel(currentButtonLabels, 'unmute')\n : getButtonLabel(currentButtonLabels, 'mute'));\n });\n }\n // Wire up hotspot popup close button to stop audio/media\n const popupCloseBtn = container.querySelector('.storysplat-hotspot-popup-close');\n if (popupCloseBtn) {\n popupCloseBtn.addEventListener('click', () => {\n stopAllHotspotAudio();\n stopHotspotPopupMedia(container);\n });\n }\n // Setup waypoint list dropdown click handlers\n if (uiElements.waypointListContainer && config.waypoints && config.waypoints.length > 0) {\n setupWaypointListClickHandlers(uiElements, (index) => {\n console.log('[StorySplat Viewer] Waypoint list: jumping to waypoint', index);\n goToWaypoint(index);\n });\n // Update active waypoint in the list when waypoint changes\n events.on('waypointChange', ({ index }: {\n index: number;\n }) => {\n updateWaypointListActive(uiElements, index);\n });\n // Set initial active state\n updateWaypointListActive(uiElements, 0);\n }\n // Emit initial progress to update UI\n events.emit('progressUpdate', { progress: currentProgress, index: currentWaypointIndex });\n // Emit initial waypoint change to show first waypoint info\n if (config.waypoints && config.waypoints.length > 0) {\n events.emit('waypointChange', {\n index: 0,\n waypoint: config.waypoints[0],\n prevIndex: -1\n });\n }\n }\n // Start autoplay if enabled via options OR via scene config\n if (options.autoPlay || config.autoPlay) {\n play();\n }\n }).catch(err => {\n console.error('[StorySplat Viewer] Failed to initialize:', err);\n // Hide preloader even on error\n if (uiElements.preloader) {\n hidePreloader(uiElements.preloader);\n }\n events.emit('error', err);\n });\n // Handle resize\n const handleResize = () => {\n app.resizeCanvas();\n };\n window.addEventListener('resize', handleResize);\n // =====================================================\n // EDITOR MUTATION API (only exposed when editor: true)\n // =====================================================\n // Track editor hotspot/light entities by ID for mutation\n const editorHotspotMap = new Map<string, pc.Entity>();\n const editorLightMap = new Map<string, pc.Entity>();\n const editorParticleMap = new Map<string, pc.Entity>();\n const editorCustomMeshMap = new Map<string, pc.Entity>();\n const editorPortalMap = new Map<string, pc.Entity>();\n // Index existing hotspot entities by ID if in editor mode\n if (isEditorMode) {\n hotspotEntities.forEach((entity) => {\n const id = entity.hotspotData?.id;\n if (id)\n editorHotspotMap.set(id, entity);\n });\n lightEntities.forEach((entity, index: number) => {\n const lightConfig = config.lights?.[index];\n const id = lightConfig?.id || lightConfig?.name || `light-${index}`;\n editorLightMap.set(id, entity);\n });\n particleEntities.forEach((entity, id) => {\n editorParticleMap.set(id, entity);\n });\n portalEntities.forEach((entity) => {\n const id = entity.portalData?.id;\n if (id)\n editorPortalMap.set(id, entity);\n });\n }\n // Return viewer instance\n const instance: ViewerInstance = {\n app,\n canvas,\n // Navigation\n goToWaypoint: (index: number) => {\n if (currentCameraMode === 'explore') {\n throw new Error('[StorySplat Viewer] goToWaypoint() requires tour mode. Call setCameraMode(\"tour\") first.');\n }\n goToWaypoint(index);\n },\n nextWaypoint: () => {\n if (currentCameraMode === 'explore') {\n throw new Error('[StorySplat Viewer] nextWaypoint() requires tour mode. Call setCameraMode(\"tour\") first.');\n }\n nextWaypoint();\n },\n prevWaypoint: () => {\n if (currentCameraMode === 'explore') {\n throw new Error('[StorySplat Viewer] prevWaypoint() requires tour mode. Call setCameraMode(\"tour\") first.');\n }\n prevWaypoint();\n },\n getCurrentWaypointIndex: () => currentWaypointIndex,\n getWaypointCount: () => config.waypoints?.length || 0,\n // Camera\n setPosition: (x, y, z) => {\n if (currentCameraMode !== 'explore') {\n throw new Error('[StorySplat Viewer] setPosition() requires explore mode. Call setCameraMode(\"explore\") first.');\n }\n if (isPlaying) {\n throw new Error('[StorySplat Viewer] setPosition() cannot be used during autoplay. Call pause() or stop() first.');\n }\n // Disable controls to flush input, set camera, sync controller state, re-enable\n cameraControls.disable();\n camera.setPosition(x, y, z);\n cameraControls.syncFromCamera();\n cameraControls.enable();\n // Re-sync on next frame to ensure controller didn't interpolate back\n requestAnimationFrame(() => {\n camera.setPosition(x, y, z);\n cameraControls.syncFromCamera();\n });\n },\n setRotation: (x, y, z) => {\n if (currentCameraMode !== 'explore') {\n throw new Error('[StorySplat Viewer] setRotation() requires explore mode. Call setCameraMode(\"explore\") first.');\n }\n if (isPlaying) {\n throw new Error('[StorySplat Viewer] setRotation() cannot be used during autoplay. Call pause() or stop() first.');\n }\n cameraControls.disable();\n camera.setEulerAngles(x, y, z);\n cameraControls.syncFromCamera();\n cameraControls.enable();\n requestAnimationFrame(() => {\n camera.setEulerAngles(x, y, z);\n cameraControls.syncFromCamera();\n });\n },\n getPosition: () => {\n const pos = camera.getPosition();\n return { x: pos.x, y: pos.y, z: pos.z };\n },\n getRotation: () => {\n const rot = camera.getEulerAngles();\n return { x: rot.x, y: rot.y, z: rot.z };\n },\n // Playback\n play: () => {\n if (currentCameraMode === 'explore') {\n throw new Error('[StorySplat Viewer] play() requires tour mode. Call setCameraMode(\"tour\") first.');\n }\n play();\n },\n pause: () => {\n if (currentCameraMode === 'explore') {\n throw new Error('[StorySplat Viewer] pause() requires tour mode. Call setCameraMode(\"tour\") first.');\n }\n pause();\n },\n stop: () => {\n if (currentCameraMode === 'explore') {\n throw new Error('[StorySplat Viewer] stop() requires tour mode. Call setCameraMode(\"tour\") first.');\n }\n stop();\n },\n isPlaying: () => isPlaying,\n // 4DGS Frame Sequence API (only functional when frameSequence is configured)\n setFrame: (index: number) => frameSequencePlayer?.setFrame(index),\n getCurrentFrame: () => frameSequencePlayer?.getCurrentFrame() ?? 0,\n getTotalFrames: () => frameSequencePlayer?.getTotalFrames() ?? 0,\n setFps: (fps: number) => frameSequencePlayer?.setFps(fps),\n getFps: () => frameSequencePlayer?.getFps() ?? 24,\n getFrameProgress: () => frameSequencePlayer?.getProgress() ?? 0,\n setFrameProgress: (progress: number) => frameSequencePlayer?.setProgress(progress),\n // Splat API\n goToOriginalSplat,\n goToSplat: async (url: string) => {\n const primaryUrl = getPrimarySplatUrl();\n // Check if it's the original splat\n if (url === primaryUrl) {\n goToOriginalSplat();\n return;\n }\n // Check if it's in additionalSplats\n const splatConfig = additionalSplats.find(s => s.url === url);\n if (!splatConfig && !preloadedSplats.has(url)) {\n // Need to preload it first\n await preloadSplat(url);\n }\n // Hide current splat\n const currentUrl = currentSplatUrl || primaryUrl;\n if (currentUrl === primaryUrl && splatEntity) {\n hideSplat(splatEntity);\n }\n else if (currentUrl) {\n const currentEntity = preloadedSplats.get(currentUrl);\n if (currentEntity)\n currentEntity.enabled = false;\n }\n // Show the requested splat\n if (!showSplat(url)) {\n throw new Error(`[StorySplat Viewer] Failed to switch to splat: ${url}`);\n }\n // Apply explore mode if applicable\n if (splatConfig) {\n applyExploreModeForSplat(splatConfig.defaultExploreMode, false);\n }\n events.emit('splatChange', { url, isOriginal: false });\n },\n getCurrentSplatUrl,\n isShowingOriginalSplat,\n getAdditionalSplats: () => additionalSplats.map(s => ({\n url: s.url,\n name: s.name,\n waypointIndex: s.waypointIndex,\n percentage: s.percentage\n })),\n // Lifecycle\n destroy: () => {\n // Set destroyed flag first to prevent async operations from completing\n isDestroyed = true;\n pause();\n // Destroy frame sequence player if exists\n if (frameSequencePlayer) {\n frameSequencePlayer.destroy();\n frameSequencePlayer = null;\n }\n window.removeEventListener('resize', handleResize);\n // Remove UI elements\n if (uiElements.preloader)\n uiElements.preloader.remove();\n if (uiElements.scrollControls)\n uiElements.scrollControls.remove();\n if (uiElements.fullscreenButton)\n uiElements.fullscreenButton.remove();\n if (uiElements.helpButton)\n uiElements.helpButton.remove();\n if (uiElements.helpPanel)\n uiElements.helpPanel.remove();\n if (uiElements.waypointInfo)\n uiElements.waypointInfo.remove();\n if (uiElements.watermark)\n uiElements.watermark.remove();\n // Remove injected styles\n const styleEl = document.getElementById('storysplat-viewer-styles');\n if (styleEl)\n styleEl.remove();\n // Remove container class\n container.classList.remove('storysplat-viewer-container');\n // Cleanup animated GIFs\n animatedGifs.forEach(gif => gif.destroy());\n animatedGifs.length = 0;\n // Cleanup custom meshes\n cleanupCustomMeshes();\n // Clear collision references from CameraControls before destroying CharacterController\n // (CharacterController.destroy() destroys the collision entities)\n cameraControls.setCollisionEntities([]);\n // Cleanup CharacterController\n if (characterController) {\n characterController.destroy();\n }\n // Cleanup Custom Script System\n const destroyScriptSys = (app as ViewerRuntimeApp).__customScriptSystem;\n if (destroyScriptSys) {\n destroyScriptSys.dispose();\n }\n // Cleanup HTML Mesh Manager\n const destroyHtmlMgr = (app as ViewerRuntimeApp).__htmlMeshManager;\n if (destroyHtmlMgr) {\n destroyHtmlMgr.destroy();\n }\n // Cleanup AR content entity\n if (arContentEntity) {\n arContentEntity.destroy();\n arContentEntity = null;\n }\n if (arContentTexture) {\n arContentTexture.destroy();\n arContentTexture = null;\n }\n app.destroy();\n canvas.remove();\n },\n resize: handleResize,\n // Portal Navigation\n navigateToScene: async (sceneId: string) => {\n const portalData = { targetSceneId: sceneId, activationMode: 'click' };\n await handlePortalNavigation(portalData);\n },\n // Mode\n setCameraMode: (mode: 'tour' | 'explore') => setCameraMode(mode),\n getCameraMode: () => currentCameraMode,\n setExploreMode: (mode: 'orbit' | 'fly') => {\n if (currentCameraMode !== 'explore') {\n throw new Error('[StorySplat Viewer] setExploreMode() requires explore mode. Call setCameraMode(\"explore\") first.');\n }\n cameraControls.setMode(mode);\n updateExploreBtnState(mode);\n },\n // Progress (direct tour progress control)\n setProgress: (progress: number) => {\n if (currentCameraMode === 'explore') {\n throw new Error('[StorySplat Viewer] setProgress() requires tour mode. Call setCameraMode(\"tour\") first.');\n }\n setProgress(progress);\n },\n getProgress: () => currentProgress,\n // Audio\n muteAll: () => muteAllAudio(),\n unmuteAll: () => unmuteAllAudio(),\n isMuted: () => globalMuted,\n // Hotspot interaction\n getHotspots: () => {\n return hotspotEntities.map((entity) => {\n const h = entity.hotspotData;\n const pos = entity.getPosition();\n return {\n id: h?.id || '',\n title: h?.title || h?.information?.substring(0, 30) || '',\n type: h?.type || 'sphere',\n position: { x: pos.x, y: pos.y, z: pos.z }\n };\n });\n },\n triggerHotspot: (id: string) => {\n const entity = hotspotEntities.find((e) => (e as ViewerRuntimeEntity).hotspotData?.id === id);\n if (!entity) {\n throw new Error(`[StorySplat Viewer] Hotspot not found: ${id}`);\n }\n const hotspot = (entity as ViewerRuntimeEntity).hotspotData;\n if (hotspot) {\n showHotspotPopup(container, hotspot, currentButtonLabels);\n }\n },\n closeHotspot: () => {\n const popupEl = container.querySelector('.storysplat-hotspot-popup') as HTMLElement;\n const overlayEl = container.querySelector('.storysplat-hotspot-overlay') as HTMLElement;\n if (popupEl)\n popupEl.classList.remove('visible');\n if (overlayEl)\n overlayEl.classList.remove('visible');\n stopAllHotspotAudio();\n stopHotspotPopupMedia(container);\n },\n // UI customization\n setButtonLabels: (labels: Partial<ButtonLabels>) => {\n // Merge new labels with existing\n currentButtonLabels = { ...currentButtonLabels, ...labels };\n // Helper to get resolved label\n const gl = (key: keyof ButtonLabels) => getButtonLabel(currentButtonLabels, key);\n // Update DOM elements that display label text\n const modeBtn = (mode: string, key: keyof ButtonLabels) => {\n const el = container.querySelector(`.storysplat-mode-btn[data-mode=\"${mode}\"]`) as HTMLElement;\n if (el)\n el.textContent = gl(key);\n };\n modeBtn('tour', 'tour');\n modeBtn('explore', 'explore');\n modeBtn('walk', 'walk');\n // Explore sub-buttons (orbit/fly)\n const orbitBtn = container.querySelector('.storysplat-explore-btn[data-explore-mode=\"orbit\"]') as HTMLElement;\n if (orbitBtn)\n orbitBtn.textContent = gl('orbit');\n const flyBtn = container.querySelector('.storysplat-explore-btn[data-explore-mode=\"fly\"]') as HTMLElement;\n if (flyBtn)\n flyBtn.textContent = gl('fly');\n // Nav buttons\n const prevBtn = container.querySelector('.storysplat-btn-prev') as HTMLElement;\n if (prevBtn)\n prevBtn.textContent = gl('previous');\n const nextBtn = container.querySelector('.storysplat-btn-next') as HTMLElement;\n if (nextBtn)\n nextBtn.textContent = gl('next');\n // Waypoint list toggle\n const wpToggle = container.querySelector('.storysplat-waypoint-list-toggle') as HTMLElement;\n if (wpToggle) {\n const svg = wpToggle.querySelector('svg');\n wpToggle.textContent = '';\n wpToggle.append(gl('waypoints'));\n if (svg)\n wpToggle.appendChild(svg);\n wpToggle.setAttribute('aria-label', gl('waypoints'));\n }\n // XR buttons (only update if not in active XR session)\n const vrBtn = container.querySelector('.storysplat-vr-btn') as HTMLElement;\n if (vrBtn && !vrBtn.classList.contains('active')) {\n vrBtn.textContent = gl('vr');\n vrBtn.setAttribute('aria-label', gl('vr'));\n }\n const arBtn = container.querySelector('.storysplat-ar-btn') as HTMLElement;\n if (arBtn && !arBtn.classList.contains('active')) {\n arBtn.textContent = gl('ar');\n arBtn.setAttribute('aria-label', gl('ar'));\n }\n // Fullscreen button aria-label\n const fsBtn = container.querySelector('.storysplat-fullscreen-btn') as HTMLElement;\n if (fsBtn)\n fsBtn.setAttribute('aria-label', gl('fullscreen'));\n // Hotspot popup close button\n const closeBtn = container.querySelector('.storysplat-hotspot-popup-close') as HTMLElement;\n if (closeBtn)\n closeBtn.textContent = gl('close');\n // Portal popup buttons\n const confirmBtn = container.querySelector('.storysplat-portal-popup-confirm') as HTMLElement;\n if (confirmBtn)\n confirmBtn.textContent = gl('yes');\n const cancelBtn = container.querySelector('.storysplat-portal-popup-cancel') as HTMLElement;\n if (cancelBtn)\n cancelBtn.textContent = gl('cancel');\n // Help panel - rebuild using safe DOM methods\n const helpPanel = container.querySelector('.storysplat-help-panel') as HTMLElement;\n if (helpPanel) {\n helpPanel.textContent = '';\n const addEl = (tag: string, text: string, bold?: boolean) => {\n const el = document.createElement(tag);\n if (bold) {\n const b = document.createElement('strong');\n b.textContent = text;\n el.appendChild(b);\n }\n else\n el.textContent = text;\n helpPanel.appendChild(el);\n return el;\n };\n const addSpacer = () => helpPanel.appendChild(document.createElement('br'));\n addEl('h3', gl('helpTitle'));\n addEl('p', gl('helpCameraModes'), true);\n addEl('p', `\\u2022 ${gl('tour')} - ${gl('helpTourDesc')}`);\n addEl('p', `\\u2022 ${gl('explore')} - ${gl('helpExploreDesc')}`);\n addEl('p', `\\u2022 ${gl('walk')} - ${gl('helpWalkDesc')}`);\n addSpacer();\n addEl('p', `${gl('tour')} Mode:`, true);\n addEl('p', '\\u2022 Scroll - Move along path');\n addEl('p', '\\u2022 Drag - Look around');\n addSpacer();\n addEl('p', `${gl('explore')} Mode:`, true);\n addEl('p', '\\u2022 LMB Drag - Orbit camera');\n addEl('p', '\\u2022 RMB Drag - Fly/look');\n addEl('p', '\\u2022 WASD/QE - Move camera');\n addEl('p', '\\u2022 Shift - Move fast');\n addEl('p', '\\u2022 Scroll/Pinch - Zoom');\n addEl('p', '\\u2022 Double-click - Focus');\n addSpacer();\n addEl('p', `${gl('walk')} Mode:`, true);\n addEl('p', '\\u2022 Click to lock mouse');\n addEl('p', '\\u2022 WASD/Arrows - Move');\n addEl('p', '\\u2022 Mouse - Look around');\n addEl('p', '\\u2022 Shift - Sprint');\n addEl('p', '\\u2022 Space - Jump');\n }\n // Help button title\n const helpBtn = container.querySelector('.storysplat-help-btn') as HTMLElement;\n if (helpBtn)\n helpBtn.setAttribute('title', gl('helpTitle'));\n },\n // Events\n on: (event: ViewerEvent, callback) => events.on(event, callback),\n off: (event: ViewerEvent, callback) => events.off(event, callback)\n };\n // If editor mode, extend instance with mutation API\n if (isEditorMode) {\n const editorInstance = instance as EditorViewerInstance;\n // Camera (editor keeps its own references for backward compat)\n editorInstance.setCameraMode = (mode: 'tour' | 'explore') => setCameraMode(mode);\n editorInstance.getCameraMode = () => currentCameraMode;\n editorInstance.getCameraControls = () => cameraControls;\n // Scene access\n editorInstance.getApp = () => app;\n editorInstance.getSplatEntity = () => splatEntity;\n editorInstance.getAllHotspotEntities = () => editorHotspotMap;\n editorInstance.getAllLightEntities = () => editorLightMap;\n editorInstance.getCamera = () => camera;\n // Progress\n editorInstance.setProgress = (progress: number) => setProgress(progress);\n editorInstance.getProgress = () => currentProgress;\n // Hotspot mutation - uses the same full rendering as createHotspots()\n editorInstance.addHotspot = (hotspot: HotspotData): string => {\n const id = hotspot.id || `hotspot-${Date.now()}`;\n const index = hotspotEntities.length;\n const entity = createSingleHotspot(hotspot, index);\n editorHotspotMap.set(id, entity);\n return id;\n };\n editorInstance.removeHotspot = (id: string) => {\n const entity = editorHotspotMap.get(id);\n if (entity) {\n entity.destroy();\n editorHotspotMap.delete(id);\n const idx = hotspotEntities.indexOf(entity);\n if (idx >= 0)\n hotspotEntities.splice(idx, 1);\n }\n };\n editorInstance.updateHotspot = (id: string, data: Partial<HotspotData>) => {\n const entity = editorHotspotMap.get(id);\n if (!entity)\n return;\n if (data.position) {\n entity.setPosition(data.position.x, data.position.y, -(data.position.z));\n }\n const existing = (entity as ViewerRuntimeEntity).hotspotData || {};\n (entity as ViewerRuntimeEntity).hotspotData = { ...existing, ...data } as HotspotData;\n };\n // Light mutation\n // Light mutation - uses the same creation functions as initCustomLights()\n editorInstance.addLight = (lightCfg: LightConfig): string => {\n const id = lightCfg.id || lightCfg.name || `light-${Date.now()}`;\n let entity: pc.Entity | null = null;\n switch (lightCfg.type) {\n case 'point':\n entity = createPointLight(lightCfg);\n break;\n case 'directional':\n entity = createDirectionalLightEntity(lightCfg);\n break;\n case 'hemispheric':\n entity = createHemisphericLight(lightCfg);\n break;\n case 'ambient':\n createAmbientLight(lightCfg);\n break;\n case 'spot':\n entity = createSpotLight(lightCfg);\n break;\n default:\n entity = createPointLight(lightCfg);\n break;\n }\n if (entity) {\n app.root.addChild(entity);\n lightEntities.push(entity);\n editorLightMap.set(id, entity);\n }\n return id;\n };\n editorInstance.removeLight = (id: string) => {\n const entity = editorLightMap.get(id);\n if (entity) {\n entity.destroy();\n editorLightMap.delete(id);\n const idx = lightEntities.indexOf(entity);\n if (idx >= 0)\n lightEntities.splice(idx, 1);\n }\n };\n editorInstance.updateLight = (id: string, data: Partial<LightConfig>) => {\n const entity = editorLightMap.get(id);\n if (!entity)\n return;\n if (data.position) {\n entity.setPosition(data.position.x ?? 0, data.position.y ?? 0, -(data.position.z ?? 0));\n }\n if (entity.light) {\n if (data.intensity !== undefined)\n entity.light.intensity = data.intensity;\n if (data.range !== undefined)\n entity.light.range = data.range;\n if (data.castShadows !== undefined)\n entity.light.castShadows = data.castShadows;\n if (data.color) {\n entity.light.color = hexToColorForLights(data.color);\n }\n }\n };\n // Custom mesh mutation - uses loadCustomMesh() for full GLB/GLTF loading\n editorInstance.addCustomMesh = (meshCfg: CustomMeshConfig): string => {\n const id = meshCfg.id || meshCfg.name || `mesh-${Date.now()}`;\n const entity = loadCustomMesh(meshCfg, customMeshEntities.size);\n if (entity) {\n editorCustomMeshMap.set(id, entity);\n }\n return id;\n };\n editorInstance.removeCustomMesh = (id: string) => {\n const entity = editorCustomMeshMap.get(id);\n if (entity) {\n // Also remove from customMeshEntities if present\n customMeshEntities.forEach((meshData, key) => {\n if (meshData.entity === entity) {\n customMeshEntities.delete(key);\n }\n });\n entity.destroy();\n editorCustomMeshMap.delete(id);\n }\n };\n // Particle mutation - uses createParticleSystemEntity() + texture loading\n editorInstance.addParticleSystem = (ps: ParticleConfig): string => {\n const id = ps.id || ps.name || `particle-${Date.now()}`;\n // Create the entity with full particle system configuration\n const entity = createParticleSystemEntity(ps);\n // Load and apply texture (async)\n const textureName = ps.particleTexture || 'flare';\n let textureUrl: string;\n let textureCacheKey: string;\n if (textureName === 'custom' && ps.customTextureUrl) {\n textureUrl = ps.customTextureUrl;\n textureCacheKey = `custom_${id}`;\n }\n else {\n textureUrl = PARTICLE_TEXTURE_URLS[textureName] || PARTICLE_TEXTURE_URLS['flare'];\n textureCacheKey = textureName;\n }\n loadParticleTexture(textureCacheKey, textureUrl).then((texture) => {\n if (entity.particlesystem) {\n entity.particlesystem.colorMap = texture;\n }\n }).catch((err) => {\n console.warn('[Editor] Failed to load particle texture:', err);\n });\n app.root.addChild(entity);\n const safeName = id.replace(/[^a-zA-Z0-9]/g, '_');\n particleEntities.set(safeName, entity);\n editorParticleMap.set(id, entity);\n return id;\n };\n editorInstance.removeParticleSystem = (id: string) => {\n const safeName = id.replace(/[^a-zA-Z0-9]/g, '_');\n const entity = editorParticleMap.get(id) || particleEntities.get(safeName);\n if (entity) {\n entity.destroy();\n editorParticleMap.delete(id);\n particleEntities.delete(safeName);\n }\n };\n // Portal mutation - uses createSinglePortal() for full rendering\n editorInstance.addPortal = (portal: PortalData): string => {\n const id = portal.id || `portal-${Date.now()}`;\n const index = portalEntities.length;\n const entity = createSinglePortal(portal, index);\n editorPortalMap.set(id, entity);\n return id;\n };\n editorInstance.removePortal = (id: string) => {\n const entity = editorPortalMap.get(id);\n if (entity) {\n // Cleanup video element if present\n const videoEl = (entity as ViewerRuntimeEntity).videoElement;\n if (videoEl) {\n videoEl.pause();\n videoEl.src = '';\n }\n entity.destroy();\n editorPortalMap.delete(id);\n const idx = portalEntities.indexOf(entity);\n if (idx >= 0)\n portalEntities.splice(idx, 1);\n }\n };\n editorInstance.updatePortal = (id: string, data: Partial<PortalData>) => {\n const entity = editorPortalMap.get(id);\n if (!entity)\n return;\n if (data.position) {\n entity.setPosition(data.position.x ?? 0, data.position.y ?? 0, -(data.position.z ?? 0));\n }\n const existing = (entity as ViewerRuntimeEntity).portalData || {};\n (entity as ViewerRuntimeEntity).portalData = { ...existing, ...data } as PortalData;\n };\n // HTML Mesh mutation - uses HtmlMeshManager\n editorInstance.addHtmlMesh = (meshConfig: HtmlMeshConfig): string => {\n const id = meshConfig.id || `html-mesh-${Date.now()}`;\n let manager = (app as ViewerRuntimeApp).__htmlMeshManager;\n if (!manager) {\n manager = setupHtmlMeshes(app, []);\n (app as ViewerRuntimeApp).__htmlMeshManager = manager;\n }\n manager.createMesh({ ...meshConfig, id, html: meshConfig.html || meshConfig.htmlContent || '' });\n return id;\n };\n editorInstance.removeHtmlMesh = (id: string) => {\n const manager = (app as ViewerRuntimeApp).__htmlMeshManager;\n if (manager) {\n manager.destroyMesh(id);\n }\n };\n // Collision Mesh mutation\n editorInstance.addCollisionMesh = (meshConfig: CollisionMeshConfig): string => {\n const id = meshConfig.id || `collision-${Date.now()}`;\n const entity = new pc.Entity(`collision-mesh-${id}`);\n const pos = meshConfig.position;\n const px = Array.isArray(pos) ? pos[0] : pos.x;\n const py = Array.isArray(pos) ? pos[1] : pos.y;\n const pz = Array.isArray(pos) ? pos[2] : pos.z;\n entity.setPosition(px, py, -(pz ?? 0));\n if (meshConfig.rotation) {\n const rot = meshConfig.rotation;\n const rx = Array.isArray(rot) ? rot[0] : rot.x;\n const ry = Array.isArray(rot) ? rot[1] : rot.y;\n const rz = Array.isArray(rot) ? rot[2] : rot.z;\n entity.setEulerAngles(rx ?? 0, ry ?? 0, rz ?? 0);\n }\n if (meshConfig.scaling) {\n const s = meshConfig.scaling;\n const sx = Array.isArray(s) ? s[0] : s.x;\n const sy = Array.isArray(s) ? s[1] : s.y;\n const sz = Array.isArray(s) ? s[2] : s.z;\n entity.setLocalScale(sx ?? 1, sy ?? 1, sz ?? 1);\n }\n // Add render component for visualization\n const meshType = meshConfig.meshType || 'cube';\n if (meshType !== 'custom') {\n const renderType = meshType === 'cube' ? 'box' : meshType === 'floor' ? 'plane' : meshType;\n entity.addComponent('render', {\n type: renderType,\n castShadows: false,\n receiveShadows: false,\n });\n // Semi-transparent green material for collision mesh visualization\n const material = new pc.StandardMaterial();\n material.diffuse = new pc.Color(0, 1, 0);\n material.opacity = 0.3;\n material.blendType = pc.BLEND_NORMAL;\n material.depthWrite = false;\n material.update();\n if (entity.render) {\n entity.render.meshInstances.forEach((mi) => { mi.material = material; });\n }\n }\n entity.addComponent('collision', {\n type: meshType === 'sphere' ? 'sphere' : 'box',\n });\n entity.enabled = meshConfig.visible !== false;\n (entity as ViewerRuntimeEntity)._collisionMeshId = id;\n app.root.addChild(entity);\n return id;\n };\n editorInstance.removeCollisionMesh = (id: string) => {\n // Find and destroy the collision mesh entity by ID\n const children = app.root.children;\n for (let i = children.length - 1; i >= 0; i--) {\n if ((children[i] as ViewerRuntimeEntity)._collisionMeshId === id) {\n children[i].destroy();\n break;\n }\n }\n };\n // Audio Emitter mutation - uses same setup as setupAudioEmitters()\n editorInstance.addAudioEmitter = (emitterConfig: AudioEmitterConfig): string => {\n const id = emitterConfig.id || `emitter-${Date.now()}`;\n const position = emitterConfig.position || { x: 0, y: 0, z: 0 };\n const audioEntity = new pc.Entity(`audio-emitter-${id}`);\n audioEntity.setPosition(position.x, position.y, position.z);\n audioEntity.addComponent('sound', {\n positional: emitterConfig.spatialSound !== false,\n refDistance: emitterConfig.refDistance || 1,\n maxDistance: emitterConfig.maxDistance || 100,\n rollOffFactor: emitterConfig.rolloffFactor || 1,\n volume: emitterConfig.volume ?? 0.5,\n });\n audioEntity.sound?.addSlot(id, {\n volume: emitterConfig.volume ?? 0.5,\n loop: emitterConfig.loop !== false,\n autoPlay: false,\n overlap: false,\n });\n if (emitterConfig.url) {\n const audioAsset = new pc.Asset(`audio-emitter-${id}`, 'audio', { url: emitterConfig.url });\n audioAsset.on('load', () => {\n if (isDestroyed)\n return;\n const slot = audioEntity.sound?.slot(id);\n if (slot) {\n slot.asset = audioAsset.id;\n if (emitterConfig.autoplay !== false) {\n slot.play();\n }\n }\n });\n app.assets.add(audioAsset);\n app.assets.load(audioAsset);\n }\n app.root.addChild(audioEntity);\n audioEmitterMap.set(id, {\n entity: audioEntity,\n config: emitterConfig,\n slotId: id,\n playing: false,\n assetReady: false,\n });\n return id;\n };\n editorInstance.removeAudioEmitter = (id: string) => {\n const data = audioEmitterMap.get(id);\n if (data) {\n const slot = data.entity.sound?.slot(data.slotId);\n if (slot)\n slot.stop();\n data.entity.destroy();\n audioEmitterMap.delete(id);\n }\n };\n editorInstance.updateAudioEmitter = (id: string, data: Partial<AudioEmitterConfig>) => {\n const emitterData = audioEmitterMap.get(id);\n if (!emitterData)\n return;\n if (data.position) {\n emitterData.entity.setPosition(data.position.x, data.position.y, data.position.z);\n }\n if (data.volume !== undefined && emitterData.entity.sound) {\n emitterData.entity.sound.volume = data.volume;\n }\n emitterData.config = { ...emitterData.config, ...data };\n };\n // Environment\n editorInstance.setSkybox = (url: string | null, rotation?: number) => {\n setSkyboxRuntime(url, rotation);\n };\n editorInstance.setBackgroundColor = (color: {\n r: number;\n g: number;\n b: number;\n }) => {\n const pcColor = new pc.Color(color.r / 255, color.g / 255, color.b / 255);\n if (camera.camera) {\n camera.camera.clearColor = pcColor;\n }\n };\n editorInstance.setFOV = (newFov: number) => {\n if (camera.camera) {\n camera.camera.fov = newFov;\n }\n };\n // Waypoints (operate on config.waypoints directly)\n editorInstance.addWaypoint = (wp: WaypointData) => {\n if (!config.waypoints)\n config.waypoints = [];\n config.waypoints.push(wp);\n };\n editorInstance.removeWaypoint = (index: number) => {\n if (config.waypoints && index >= 0 && index < config.waypoints.length) {\n config.waypoints.splice(index, 1);\n }\n };\n editorInstance.updateWaypoint = (index: number, data: Partial<WaypointData>) => {\n if (config.waypoints && index >= 0 && index < config.waypoints.length) {\n Object.assign(config.waypoints[index], data);\n }\n };\n editorInstance.rebuildTourPath = () => {\n // The tour path is computed from config.waypoints on-the-fly in updateCameraFromProgress\n // No explicit rebuild needed - the next progress update will use the latest waypoints\n console.log('[Editor] Tour path will use updated waypoints on next progress update');\n };\n return editorInstance;\n }\n return instance;\n}\n/**\n * Create viewer from scene URL\n */\nexport async function createViewerFromUrl(container: HTMLElement, jsonUrl: string, options?: ViewerOptions): Promise<ViewerInstance> {\n console.log('[StorySplat Viewer] Fetching scene from:', jsonUrl);\n const response = await fetch(jsonUrl);\n if (!response.ok) {\n throw new Error(`Failed to fetch scene: ${response.statusText}`);\n }\n const scene: SceneData = await response.json();\n console.log('[StorySplat Viewer] Scene data loaded:', scene);\n return createViewer(container, scene, options);\n}\n// Utility functions\nfunction lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\nfunction lerpAngle(a: number, b: number, t: number): number {\n let diff = b - a;\n while (diff > 180)\n diff -= 360;\n while (diff < -180)\n diff += 360;\n return a + diff * t;\n}\nfunction easeInOutCubic(t: number): number {\n return t < 0.5\n ? 4 * t * t * t\n : 1 - Math.pow(-2 * t + 2, 3) / 2;\n}\n","/**\n * Create viewer from StorySplat Scene ID\n *\n * This module provides functionality to create a viewer by fetching\n * scene data from the StorySplat API using a scene ID.\n */\n\nimport { createViewer } from './createViewer';\nimport type { ViewerInstance, ViewerOptions, SceneData, ViewerLoadedData } from '../types';\n\n/**\n * Track embedded scene usage (views and bandwidth)\n * Sends tracking data to StorySplat API for analytics\n */\nasync function trackEmbedUsage(\n baseUrl: string,\n sceneId: string,\n ownerId: string,\n type: 'view' | 'bandwidth',\n bytes?: number\n): Promise<void> {\n try {\n const response = await fetch(`${baseUrl}/api/track-embed`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n sceneId,\n ownerId,\n type,\n ...(type === 'bandwidth' && bytes ? { bytes } : {})\n })\n });\n\n if (!response.ok) {\n console.warn(`[StorySplat] Failed to track ${type}:`, response.status);\n } else {\n console.log(`[StorySplat] Tracked ${type}${type === 'bandwidth' ? ` (${(bytes! / 1024 / 1024).toFixed(2)}MB)` : ''}`);\n }\n } catch (error) {\n // Silently fail - tracking shouldn't break the viewer\n console.warn(`[StorySplat] Error tracking ${type}:`, error);\n }\n}\n\n/**\n * Options for createViewerFromSceneId\n */\nexport interface ViewerFromSceneIdOptions extends ViewerOptions {\n /**\n * Base URL for the StorySplat API\n * @default 'https://discover.storysplat.com'\n */\n baseUrl?: string;\n\n /**\n * API key for accessing private scenes (future feature)\n */\n apiKey?: string;\n}\n\n/**\n * API response format from /api/scene/{sceneId}\n */\ninterface SceneApiResponse {\n success: boolean;\n data: SceneData;\n meta: {\n sceneId: string;\n ownerId: string; // User ID for tracking\n name: string;\n description?: string;\n userName?: string; // May be undefined for anonymous/deleted users\n userSlug?: string; // May be undefined for anonymous/deleted users\n thumbnailUrl: string;\n splatUrl: string;\n htmlUrl: string;\n playcanvasHtmlUrl?: string;\n views: number;\n createdAt: string | null;\n };\n}\n\n/**\n * Error thrown when scene fetching fails\n */\nexport class SceneNotFoundError extends Error {\n constructor(sceneId: string) {\n super(`Scene not found: ${sceneId}`);\n this.name = 'SceneNotFoundError';\n }\n}\n\n/**\n * Error thrown when API request fails\n */\nexport class SceneApiError extends Error {\n public statusCode: number;\n\n constructor(message: string, statusCode: number) {\n super(message);\n this.name = 'SceneApiError';\n this.statusCode = statusCode;\n }\n}\n\n/**\n * Get the default API base URL from environment or fallback to production\n */\nfunction getDefaultBaseUrl(): string {\n // Check for environment variable (works in Node.js, bundlers with define, etc.)\n if (typeof process !== 'undefined' && process.env?.STORYSPLAT_API_URL) {\n return process.env.STORYSPLAT_API_URL;\n }\n // Check for window-based config (for browser environments)\n const browserWindow = typeof window !== 'undefined'\n ? (window as Window & { __STORYSPLAT_API_URL__?: string })\n : undefined;\n if (browserWindow?.__STORYSPLAT_API_URL__) {\n return browserWindow.__STORYSPLAT_API_URL__;\n }\n // Default to production\n return 'https://discover.storysplat.com';\n}\n\n/**\n * Default API base URL\n */\nconst DEFAULT_BASE_URL = getDefaultBaseUrl();\n\n/**\n * Create a viewer from a StorySplat scene ID\n *\n * This function fetches scene data from the StorySplat API and creates\n * an embedded viewer. The scene must be public and owned by a user\n * with a public profile.\n *\n * @param container - HTML element to render the viewer in\n * @param sceneId - StorySplat scene ID from your dashboard\n * @param options - Viewer options including API configuration\n * @returns Promise resolving to ViewerInstance\n *\n * @example\n * ```typescript\n * import { createViewerFromSceneId } from 'storysplat-viewer';\n *\n * const viewer = await createViewerFromSceneId(\n * document.getElementById('viewer')!,\n * 'YOUR_SCENE_ID'\n * );\n *\n * // Control playback\n * viewer.play();\n * viewer.pause();\n *\n * // Navigate\n * viewer.nextWaypoint();\n * viewer.goToWaypoint(2);\n *\n * // Listen for events\n * viewer.on('ready', () => console.log('Viewer ready!'));\n * ```\n */\nexport async function createViewerFromSceneId(\n container: HTMLElement,\n sceneId: string,\n options: ViewerFromSceneIdOptions = {}\n): Promise<ViewerInstance> {\n const baseUrl = options.baseUrl || DEFAULT_BASE_URL;\n\n console.log(`[StorySplat Viewer] Fetching scene: ${sceneId}`);\n\n // Build request URL\n const apiUrl = `${baseUrl}/api/scene/${encodeURIComponent(sceneId)}`;\n\n // Build headers\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n };\n\n // Add API key if provided (for future private scene support)\n if (options.apiKey) {\n headers['Authorization'] = `Bearer ${options.apiKey}`;\n }\n\n // Fetch scene data\n const response = await fetch(apiUrl, {\n method: 'GET',\n headers,\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n throw new SceneNotFoundError(sceneId);\n }\n\n const errorText = await response.text();\n let errorMessage: string;\n\n try {\n const errorJson = JSON.parse(errorText);\n errorMessage = errorJson.error || `API error: ${response.status}`;\n } catch {\n errorMessage = `API error: ${response.status}`;\n }\n\n throw new SceneApiError(errorMessage, response.status);\n }\n\n const apiResponse: SceneApiResponse = await response.json();\n\n if (!apiResponse.success || !apiResponse.data) {\n throw new SceneApiError('Invalid API response format', 500);\n }\n\n console.log(`[StorySplat Viewer] Scene loaded: \"${apiResponse.meta.name}\"`);\n\n // Extract tracking info\n const ownerId = apiResponse.meta.ownerId;\n\n // Merge scene metadata into scene data if not present\n const sceneData: SceneData = {\n ...apiResponse.data,\n // Ensure these are set from metadata if not in scene data\n name: apiResponse.data.name || apiResponse.meta.name,\n thumbnailUrl: apiResponse.data.thumbnailUrl || apiResponse.meta.thumbnailUrl,\n };\n\n // Extract viewer-specific options (remove API-specific ones)\n const { baseUrl: _baseUrl, apiKey: _apiKey, ...viewerOptions } = options;\n\n // Create the viewer\n const viewer = createViewer(container, sceneData, viewerOptions);\n\n // Track view (fire and forget)\n trackEmbedUsage(baseUrl, sceneId, ownerId, 'view');\n\n // Track bandwidth when scene loads (only for StorySplat-hosted files)\n let bandwidthTracked = false;\n viewer.on('loaded', (data?: ViewerLoadedData) => {\n if (bandwidthTracked) return; // Only track once\n bandwidthTracked = true;\n\n if (data && data.bandwidthUsed > 0 && data.isStorySplatHosted) {\n trackEmbedUsage(baseUrl, sceneId, ownerId, 'bandwidth', data.bandwidthUsed);\n } else {\n console.log('[StorySplat] Bandwidth not tracked (self-hosted or no data)');\n }\n });\n\n return viewer;\n}\n\n/**\n * Fetch scene metadata without creating a viewer\n *\n * Useful for displaying scene information before loading the full viewer.\n *\n * @param sceneId - StorySplat scene ID\n * @param options - API configuration options\n * @returns Promise resolving to scene metadata\n *\n * @example\n * ```typescript\n * import { fetchSceneMeta } from 'storysplat-viewer';\n *\n * const meta = await fetchSceneMeta('YOUR_SCENE_ID');\n * console.log(`Scene: ${meta.name} by ${meta.userName}`);\n * console.log(`Views: ${meta.views}`);\n * ```\n */\nexport async function fetchSceneMeta(\n sceneId: string,\n options: { baseUrl?: string; apiKey?: string } = {}\n): Promise<{\n name: string;\n description: string;\n thumbnailUrl: string;\n userName: string; // Defaults to 'Unknown' if not available\n userSlug: string; // Defaults to 'unknown' if not available\n views: number;\n tags: string[];\n category?: string;\n createdAt: string | null;\n}> {\n const baseUrl = options.baseUrl || DEFAULT_BASE_URL;\n const apiUrl = `${baseUrl}/api/scene/${encodeURIComponent(sceneId)}/meta`;\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json',\n };\n\n if (options.apiKey) {\n headers['Authorization'] = `Bearer ${options.apiKey}`;\n }\n\n const response = await fetch(apiUrl, {\n method: 'GET',\n headers,\n });\n\n if (!response.ok) {\n if (response.status === 404) {\n throw new SceneNotFoundError(sceneId);\n }\n throw new SceneApiError(`API error: ${response.status}`, response.status);\n }\n\n const data = await response.json();\n\n // Ensure userName and userSlug have fallback values\n return {\n ...data,\n userName: data.userName || 'Unknown',\n userSlug: data.userSlug || 'unknown',\n };\n}\n","/**\n * GizmoManager - PlayCanvas Gizmo wrapper for editor\n *\n * Provides translate, rotate, and scale gizmos for 3D object manipulation.\n * API modeled after BabylonJS GizmoManager for easier migration.\n */\nimport * as pc from 'playcanvas';\ninterface GizmoNamespaceWithLayer {\n createLayer: (app: pc.Application) => pc.Layer;\n}\nexport type GizmoMode = 'translate' | 'rotate' | 'scale' | 'none';\nexport interface GizmoManagerConfig {\n snap?: boolean;\n snapIncrement?: number;\n coordSpace?: 'world' | 'local';\n}\nexport interface TransformEvent {\n entity: pc.Entity | null;\n position?: pc.Vec3;\n rotation?: pc.Quat;\n scale?: pc.Vec3;\n}\nexport class GizmoManager {\n private app: pc.Application;\n private camera: pc.CameraComponent;\n private gizmoLayer: pc.Layer;\n private translateGizmo: pc.TranslateGizmo | null = null;\n private rotateGizmo: pc.RotateGizmo | null = null;\n private scaleGizmo: pc.ScaleGizmo | null = null;\n private activeGizmo: pc.TranslateGizmo | pc.RotateGizmo | pc.ScaleGizmo | null = null;\n private attachedEntity: pc.Entity | null = null;\n private _mode: GizmoMode = 'none';\n // Event callbacks\n private onTransformStart?: (event: TransformEvent) => void;\n private onTransformMove?: (event: TransformEvent) => void;\n private onTransformEnd?: (event: TransformEvent) => void;\n constructor(app: pc.Application, camera: pc.CameraComponent, config: GizmoManagerConfig = {}) {\n this.app = app;\n this.camera = camera;\n // Create the gizmo layer\n this.gizmoLayer = (pc.Gizmo as unknown as GizmoNamespaceWithLayer).createLayer(app);\n // Create all gizmos\n this.translateGizmo = new pc.TranslateGizmo(camera, this.gizmoLayer);\n this.rotateGizmo = new pc.RotateGizmo(camera, this.gizmoLayer);\n this.scaleGizmo = new pc.ScaleGizmo(camera, this.gizmoLayer);\n // Apply config\n this.setSnap(config.snap ?? false, config.snapIncrement ?? 1);\n this.setCoordSpace(config.coordSpace ?? 'world');\n // Wire up events\n this.setupEvents(this.translateGizmo);\n this.setupEvents(this.rotateGizmo);\n this.setupEvents(this.scaleGizmo);\n }\n private setupEvents(gizmo: pc.TranslateGizmo | pc.RotateGizmo | pc.ScaleGizmo): void {\n gizmo.on('transform:start', () => {\n this.onTransformStart?.({\n entity: this.attachedEntity,\n position: this.attachedEntity?.getPosition().clone(),\n rotation: this.attachedEntity?.getRotation().clone(),\n scale: this.attachedEntity?.getLocalScale().clone()\n });\n });\n gizmo.on('transform:move', () => {\n this.onTransformMove?.({\n entity: this.attachedEntity,\n position: this.attachedEntity?.getPosition().clone(),\n rotation: this.attachedEntity?.getRotation().clone(),\n scale: this.attachedEntity?.getLocalScale().clone()\n });\n });\n gizmo.on('transform:end', () => {\n this.onTransformEnd?.({\n entity: this.attachedEntity,\n position: this.attachedEntity?.getPosition().clone(),\n rotation: this.attachedEntity?.getRotation().clone(),\n scale: this.attachedEntity?.getLocalScale().clone()\n });\n });\n }\n /**\n * Get current gizmo mode\n */\n get mode(): GizmoMode {\n return this._mode;\n }\n /**\n * Set the active gizmo mode\n */\n setMode(mode: GizmoMode): void {\n this._mode = mode;\n // Detach current gizmo\n this.activeGizmo?.detach();\n this.activeGizmo = null;\n // Activate the new gizmo\n switch (mode) {\n case 'translate':\n this.activeGizmo = this.translateGizmo;\n break;\n case 'rotate':\n this.activeGizmo = this.rotateGizmo;\n break;\n case 'scale':\n this.activeGizmo = this.scaleGizmo;\n break;\n case 'none':\n default:\n break;\n }\n // Re-attach to current entity if any\n if (this.activeGizmo && this.attachedEntity) {\n this.activeGizmo.attach([this.attachedEntity]);\n }\n }\n /**\n * Attach gizmo to an entity\n */\n attach(entity: pc.Entity | null): void {\n this.attachedEntity = entity;\n if (this.activeGizmo) {\n if (entity) {\n this.activeGizmo.attach([entity]);\n }\n else {\n this.activeGizmo.detach();\n }\n }\n }\n /**\n * Attach to mesh by finding the parent entity\n * (Compatibility with BabylonJS pattern)\n */\n attachToMesh(mesh: pc.Entity | null): void {\n this.attach(mesh);\n }\n /**\n * Detach gizmo from current entity\n */\n detach(): void {\n this.attachedEntity = null;\n this.activeGizmo?.detach();\n }\n /**\n * Enable/disable position gizmo\n * (BabylonJS compatibility)\n */\n set positionGizmoEnabled(enabled: boolean) {\n if (enabled && this._mode !== 'translate') {\n this.setMode('translate');\n }\n else if (!enabled && this._mode === 'translate') {\n this.setMode('none');\n }\n }\n get positionGizmoEnabled(): boolean {\n return this._mode === 'translate';\n }\n /**\n * Enable/disable rotation gizmo\n * (BabylonJS compatibility)\n */\n set rotationGizmoEnabled(enabled: boolean) {\n if (enabled && this._mode !== 'rotate') {\n this.setMode('rotate');\n }\n else if (!enabled && this._mode === 'rotate') {\n this.setMode('none');\n }\n }\n get rotationGizmoEnabled(): boolean {\n return this._mode === 'rotate';\n }\n /**\n * Enable/disable scale gizmo\n * (BabylonJS compatibility)\n */\n set scaleGizmoEnabled(enabled: boolean) {\n if (enabled && this._mode !== 'scale') {\n this.setMode('scale');\n }\n else if (!enabled && this._mode === 'scale') {\n this.setMode('none');\n }\n }\n get scaleGizmoEnabled(): boolean {\n return this._mode === 'scale';\n }\n /**\n * Set snap enabled and increment\n */\n setSnap(enabled: boolean, increment?: number): void {\n if (this.translateGizmo) {\n this.translateGizmo.snap = enabled;\n if (increment !== undefined) {\n this.translateGizmo.snapIncrement = increment;\n }\n }\n if (this.rotateGizmo) {\n this.rotateGizmo.snap = enabled;\n if (increment !== undefined) {\n this.rotateGizmo.snapIncrement = increment;\n }\n }\n if (this.scaleGizmo) {\n this.scaleGizmo.snap = enabled;\n if (increment !== undefined) {\n this.scaleGizmo.snapIncrement = increment;\n }\n }\n }\n /**\n * Set coordinate space (world or local)\n */\n setCoordSpace(space: 'world' | 'local'): void {\n if (this.translateGizmo) {\n this.translateGizmo.coordSpace = space;\n }\n if (this.rotateGizmo) {\n this.rotateGizmo.coordSpace = space;\n }\n if (this.scaleGizmo) {\n this.scaleGizmo.coordSpace = space;\n }\n }\n /**\n * Set transform event callbacks\n */\n onTransform(callbacks: {\n start?: (event: TransformEvent) => void;\n move?: (event: TransformEvent) => void;\n end?: (event: TransformEvent) => void;\n }): void {\n this.onTransformStart = callbacks.start;\n this.onTransformMove = callbacks.move;\n this.onTransformEnd = callbacks.end;\n }\n /**\n * Update gizmo (call each frame if needed)\n */\n update(): void {\n // PlayCanvas gizmos auto-update, but we can force refresh if needed\n this.activeGizmo?.update();\n }\n /**\n * Destroy the gizmo manager and clean up resources\n */\n destroy(): void {\n this.translateGizmo?.destroy();\n this.rotateGizmo?.destroy();\n this.scaleGizmo?.destroy();\n this.translateGizmo = null;\n this.rotateGizmo = null;\n this.scaleGizmo = null;\n this.activeGizmo = null;\n this.attachedEntity = null;\n }\n}\n","/**\n * SelectionManager - PlayCanvas entity selection for editor\n *\n * Handles raycasting, selection state, and visual feedback.\n */\n\nimport * as pc from 'playcanvas';\n\nexport interface SelectionEvent {\n entity: pc.Entity | null;\n previousEntity: pc.Entity | null;\n}\n\nexport interface SelectionManagerConfig {\n highlightColor?: pc.Color;\n multiSelect?: boolean;\n}\n\nexport class SelectionManager {\n private app: pc.Application;\n private camera: pc.Entity;\n private picker: pc.Picker;\n\n private selectedEntities: Set<pc.Entity> = new Set();\n private highlightedEntity: pc.Entity | null = null;\n private config: SelectionManagerConfig;\n\n // Original materials storage for highlight restoration\n private originalMaterials: Map<pc.Entity, Map<number, pc.Material>> = new Map();\n\n // Event callbacks\n private onSelectionChange?: (event: SelectionEvent) => void;\n\n constructor(\n app: pc.Application,\n camera: pc.Entity,\n config: SelectionManagerConfig = {}\n ) {\n this.app = app;\n this.camera = camera;\n this.config = {\n highlightColor: config.highlightColor ?? new pc.Color(0.2, 0.5, 1, 1),\n multiSelect: config.multiSelect ?? false\n };\n\n // Create picker with depth enabled for GSplat support (PlayCanvas 2.14+)\n this.picker = new pc.Picker(app, 1, 1, true);\n }\n\n /**\n * Pick entity at screen coordinates\n */\n async pickAtScreenPosition(x: number, y: number): Promise<pc.Entity | null> {\n const canvas = this.app.graphicsDevice.canvas;\n const cameraComponent = this.camera.camera;\n\n if (!cameraComponent || !canvas) {\n return null;\n }\n\n // Resize picker to a small region around the click point\n const pickerScale = 0.25;\n const pickerWidth = Math.max(1, Math.floor(canvas.clientWidth * pickerScale));\n const pickerHeight = Math.max(1, Math.floor(canvas.clientHeight * pickerScale));\n\n this.picker.resize(pickerWidth, pickerHeight);\n\n // Get the world layer\n const worldLayer = this.app.scene.layers.getLayerByName('World');\n if (!worldLayer) {\n return null;\n }\n\n // Prepare the picker\n this.picker.prepare(cameraComponent, this.app.scene, [worldLayer]);\n\n // Scale coordinates to picker size\n const pickerX = Math.floor(x * pickerScale);\n const pickerY = Math.floor(y * pickerScale);\n\n // Get selection at point\n const selection = this.picker.getSelection(pickerX, pickerY);\n\n if (selection && selection.length > 0) {\n // Return the first mesh instance's entity\n const meshInstance = selection[0] as pc.MeshInstance;\n if (meshInstance && meshInstance.node) {\n // Find the top-level entity (might be nested)\n let entity = meshInstance.node as pc.Entity;\n while (entity.parent && entity.parent !== this.app.root) {\n const parent = entity.parent as pc.Entity;\n // Stop if parent has a specific tag or is a root-level scene entity\n if (parent.tags?.has('selectable') || parent.tags?.has('hotspot') ||\n parent.tags?.has('waypoint') || parent.tags?.has('light')) {\n entity = parent;\n break;\n }\n entity = parent;\n }\n return entity;\n }\n }\n\n return null;\n }\n\n /**\n * Get world point at screen coordinates (for double-click focus)\n */\n async getWorldPointAtScreen(x: number, y: number): Promise<pc.Vec3 | null> {\n const canvas = this.app.graphicsDevice.canvas;\n const cameraComponent = this.camera.camera;\n\n if (!cameraComponent || !canvas) {\n return null;\n }\n\n const pickerScale = 0.25;\n const pickerWidth = Math.max(1, Math.floor(canvas.clientWidth * pickerScale));\n const pickerHeight = Math.max(1, Math.floor(canvas.clientHeight * pickerScale));\n\n this.picker.resize(pickerWidth, pickerHeight);\n\n const worldLayer = this.app.scene.layers.getLayerByName('World');\n if (!worldLayer) {\n return null;\n }\n\n this.picker.prepare(cameraComponent, this.app.scene, [worldLayer]);\n\n const pickerX = Math.floor(x * pickerScale);\n const pickerY = Math.floor(y * pickerScale);\n\n try {\n // Use async world point method for GSplat support\n const worldPoint = await this.picker.getWorldPointAsync(pickerX, pickerY);\n return worldPoint || null;\n } catch (err) {\n return null;\n }\n }\n\n /**\n * Select an entity\n */\n select(entity: pc.Entity | null, additive: boolean = false): void {\n const previousEntity = this.getSelectedEntity();\n\n if (!additive) {\n // Clear previous selection\n this.deselectAll();\n }\n\n if (entity) {\n this.selectedEntities.add(entity);\n this.applyHighlight(entity);\n }\n\n this.onSelectionChange?.({\n entity,\n previousEntity\n });\n }\n\n /**\n * Deselect a specific entity\n */\n deselect(entity: pc.Entity): void {\n if (this.selectedEntities.has(entity)) {\n this.selectedEntities.delete(entity);\n this.removeHighlight(entity);\n\n this.onSelectionChange?.({\n entity: this.getSelectedEntity(),\n previousEntity: entity\n });\n }\n }\n\n /**\n * Deselect all entities\n */\n deselectAll(): void {\n const previousEntity = this.getSelectedEntity();\n\n this.selectedEntities.forEach(entity => {\n this.removeHighlight(entity);\n });\n this.selectedEntities.clear();\n\n if (previousEntity) {\n this.onSelectionChange?.({\n entity: null,\n previousEntity\n });\n }\n }\n\n /**\n * Check if an entity is selected\n */\n isSelected(entity: pc.Entity): boolean {\n return this.selectedEntities.has(entity);\n }\n\n /**\n * Get the first selected entity (for single selection mode)\n */\n getSelectedEntity(): pc.Entity | null {\n const entities = Array.from(this.selectedEntities);\n return entities.length > 0 ? entities[0] : null;\n }\n\n /**\n * Get all selected entities\n */\n getSelectedEntities(): pc.Entity[] {\n return Array.from(this.selectedEntities);\n }\n\n /**\n * Apply visual highlight to entity\n */\n private applyHighlight(entity: pc.Entity): void {\n // Store original materials and apply highlight\n if (!entity.render) return;\n\n const materialMap = new Map<number, pc.Material>();\n entity.render.meshInstances.forEach((mi, index) => {\n if (mi.material) {\n materialMap.set(index, mi.material);\n\n // Create highlighted material clone\n const highlightMaterial = mi.material.clone();\n if (highlightMaterial instanceof pc.StandardMaterial) {\n highlightMaterial.emissive = this.config.highlightColor!;\n highlightMaterial.emissiveIntensity = 0.3;\n highlightMaterial.update();\n }\n mi.material = highlightMaterial;\n }\n });\n\n this.originalMaterials.set(entity, materialMap);\n }\n\n /**\n * Remove visual highlight from entity\n */\n private removeHighlight(entity: pc.Entity): void {\n const materialMap = this.originalMaterials.get(entity);\n if (!materialMap || !entity.render) return;\n\n entity.render.meshInstances.forEach((mi, index) => {\n const originalMaterial = materialMap.get(index);\n if (originalMaterial) {\n mi.material = originalMaterial;\n }\n });\n\n this.originalMaterials.delete(entity);\n }\n\n /**\n * Set selection change callback\n */\n onSelect(callback: (event: SelectionEvent) => void): void {\n this.onSelectionChange = callback;\n }\n\n /**\n * Handle pointer down event for selection\n */\n async handlePointerDown(event: MouseEvent | TouchEvent, additive: boolean = false): Promise<pc.Entity | null> {\n let x: number, y: number;\n\n if (event instanceof MouseEvent) {\n x = event.clientX;\n y = event.clientY;\n } else {\n const touch = event.touches[0];\n x = touch.clientX;\n y = touch.clientY;\n }\n\n const entity = await this.pickAtScreenPosition(x, y);\n\n if (entity) {\n this.select(entity, additive);\n } else if (!additive) {\n this.deselectAll();\n }\n\n return entity;\n }\n\n /**\n * Destroy the selection manager\n */\n destroy(): void {\n this.deselectAll();\n this.selectedEntities.clear();\n this.originalMaterials.clear();\n }\n}\n","/**\n * EditorCameraController - Edit-mode orbit camera for the StorySplat editor\n *\n * Provides an independent orbit camera for scene editing, separate from the\n * tour/explore camera. Also renders an FPV camera visualization mesh showing\n * where the tour camera is positioned.\n *\n * Only instantiated by the editor, never by the viewer.\n */\n\nimport * as pc from 'playcanvas';\nimport { CameraControls } from '../dynamic-viewer/CameraControls';\n\nexport interface EditorCameraControllerConfig {\n /** Initial orbit distance from target */\n orbitDistance?: number;\n /** Movement speed */\n moveSpeed?: number;\n /** Rotation sensitivity */\n rotateSensitivity?: number;\n /** Whether to show FPV camera visualization */\n showFPVVisualization?: boolean;\n}\n\nexport class EditorCameraController {\n private app: pc.Application;\n private cameraControls: CameraControls;\n private editCamera: pc.Entity;\n private tourCamera: pc.Entity;\n private fpvEntity: pc.Entity | null = null;\n private _enabled = false;\n private config: EditorCameraControllerConfig;\n\n constructor(\n app: pc.Application,\n tourCamera: pc.Entity,\n cameraControls: CameraControls,\n config: EditorCameraControllerConfig = {}\n ) {\n this.app = app;\n this.tourCamera = tourCamera;\n this.cameraControls = cameraControls;\n this.config = config;\n\n // Create a separate edit camera entity\n this.editCamera = new pc.Entity('editCamera');\n const tourCam = tourCamera.camera;\n this.editCamera.addComponent('camera', {\n clearColor: tourCam?.clearColor || new pc.Color(0.1, 0.1, 0.1),\n fov: tourCam?.fov || 60,\n nearClip: tourCam?.nearClip || 0.1,\n farClip: tourCam?.farClip || 1000\n });\n\n // Position edit camera near tour camera initially\n const pos = tourCamera.getPosition();\n this.editCamera.setPosition(pos.x, pos.y + 5, pos.z + 10);\n this.editCamera.lookAt(pos);\n this.editCamera.enabled = false;\n\n app.root.addChild(this.editCamera);\n\n // Create FPV visualization (small cone/pyramid showing tour camera)\n if (config.showFPVVisualization !== false) {\n this.createFPVVisualization();\n }\n }\n\n private createFPVVisualization(): void {\n this.fpvEntity = new pc.Entity('fpv-camera-viz');\n\n // Use a cone to represent the camera frustum\n this.fpvEntity.addComponent('render', {\n type: 'cone',\n castShadows: false,\n receiveShadows: false\n });\n\n this.fpvEntity.setLocalScale(0.3, 0.5, 0.3);\n this.fpvEntity.enabled = false;\n this.app.root.addChild(this.fpvEntity);\n\n // Update FPV position each frame\n this.app.on('update', () => {\n if (this.fpvEntity && this._enabled) {\n const tourPos = this.tourCamera.getPosition();\n const tourRot = this.tourCamera.getRotation();\n this.fpvEntity.setPosition(tourPos);\n this.fpvEntity.setRotation(tourRot);\n // Rotate to point cone forward (cone points up by default)\n this.fpvEntity.rotateLocal(90, 0, 0);\n }\n });\n }\n\n /** Enable edit camera mode (switches from tour camera to edit camera) */\n enable(): void {\n this._enabled = true;\n this.tourCamera.enabled = false;\n this.editCamera.enabled = true;\n this.cameraControls.disable();\n\n // Position edit camera from current tour camera\n const pos = this.tourCamera.getPosition();\n this.editCamera.setPosition(pos.x, pos.y + 5, pos.z + 10);\n this.editCamera.lookAt(pos);\n\n if (this.fpvEntity) {\n this.fpvEntity.enabled = true;\n }\n }\n\n /** Disable edit camera mode (switches back to tour camera) */\n disable(): void {\n this._enabled = false;\n this.editCamera.enabled = false;\n this.tourCamera.enabled = true;\n\n if (this.fpvEntity) {\n this.fpvEntity.enabled = false;\n }\n }\n\n get enabled(): boolean {\n return this._enabled;\n }\n\n /** Get the edit camera entity */\n getCamera(): pc.Entity {\n return this.editCamera;\n }\n\n /** Focus edit camera on a target point */\n focusOn(target: pc.Vec3): void {\n const dist = this.config.orbitDistance || 10;\n this.editCamera.setPosition(target.x, target.y + dist * 0.5, target.z + dist);\n this.editCamera.lookAt(target);\n }\n\n /** Sync edit camera clear color with tour camera */\n syncClearColor(): void {\n if (this.editCamera.camera && this.tourCamera.camera) {\n this.editCamera.camera.clearColor = this.tourCamera.camera.clearColor;\n }\n }\n\n /** Update FOV on both cameras */\n setFOV(fov: number): void {\n if (this.editCamera.camera) this.editCamera.camera.fov = fov;\n }\n\n destroy(): void {\n this.disable();\n this.editCamera.destroy();\n if (this.fpvEntity) {\n this.fpvEntity.destroy();\n this.fpvEntity = null;\n }\n }\n}\n"],"names":["escapeHtml","str","replace","escapeForInlineScript","generateHTML","sceneData","options","cdnUrl","title","name","description","faviconUrl","customCSS","lazyLoad","optionsLazyLoad","lazyLoadButtonText","optionsLazyLoadButtonText","uiOptions","faviconTag","customCSSBlock","sceneDataJSON","JSON","stringify","key","value","HTMLElement","async","generateHTMLFromUrl","jsonUrl","response","fetch","ok","Error","statusText","json","transformSceneToExportProps","scene","originalUrl","loadedModelUrl","splatUrl","sogUrl","sogModelUrl","compressedPlyUrl","lodMetaUrl","legacySkyboxUrl","activeSkyboxUrl","skyboxUrl","undefined","resolvedSkybox","skybox","url","rotation","skyboxRotation","originalExt","split","pop","toLowerCase","isPlayCanvasCompatible","includes","console","warn","log","original","selected","waypoints","map","wp","fov","Math","PI","info","interactions","infoInteraction","find","i","type","text","data","maybe","getInteractionText","position","x","y","z","duration","triggerDistance","resolvedParticles","Array","isArray","particleSystems","length","particles","sceneId","userId","userName","thumbnailUrl","fallbackUrls","filter","scale","splatScale","splatPosition","cameraPosition","splatRotation","cameraRotation","invertXScale","invertYScale","hotspots","portals","customMeshes","htmlMeshes","lights","collisionMeshesData","playerHeight","uiColor","showStartExperience","showWatermark","hideFullscreenButton","hideInfoButton","hideMuteButton","hideHelpButton","hideNavigator","showWaypointList","hideWatermark","watermarkText","watermarkLink","buttonPosition","buttonLabels","customPreloaderLogoUrl","lazyLoadThumbnailUrl","lazyLoadThumbnailType","uiType","debugMode","defaultCameraMode","allowedCameraModes","cameraMovementSpeed","cameraRotationSensitivity","cameraDamping","invertCameraRotation","includeScrollControls","scrollButtonMode","scrollAmount","scrollSpeed","transitionSpeed","autoPlayEnabled","autoplaySpeed","loopMode","includeXR","xrMode","customScript","templateType","additionalSplats","keepMeshesInMemory","initialSplatExploreMode","audioEmitters","frameSequence","exportPropsToViewerConfig","props","cameraMode","autoPlay","nearClip","minClipPlane","farClip","maxClipPlane","DEFAULT_BUTTON_LABELS","tour","explore","hybrid","walk","orbit","fly","next","previous","startExperience","fullscreen","mute","unmute","close","yes","cancel","switchScenes","hotspotDefaultTitle","openExternalLink","vr","ar","exitVr","exitAr","loading","loadingScene","helpTitle","helpCameraModes","helpTourDesc","helpExploreDesc","helpWalkDesc","errorWebGLTitle","errorWebGLMessage","percentageFormat","getButtonLabel","labels","injectStyles","template","existingStyle","document","getElementById","remove","style","createElement","id","textContent","generateTemplateOverrides","generateViewerStyles","head","appendChild","createPreloader","container","customLogoUrl","preloader","className","useCustomLogo","innerHTML","Promise","resolve","customElements","get","existingScript","querySelector","addEventListener","script","src","onload","hidePreloader","classList","add","setTimeout","connectUIToViewer","elements","viewer","prevBtn","scrollControls","nextBtn","playBtn","prevWaypoint","nextWaypoint","updatePlayButtonIcon","isPlaying","pause","play","on","helpButton","helpPanel","toggle","fullscreenButton","test","navigator","userAgent","platform","maxTouchPoints","display","parentElement","doc","fullscreenElement","webkitFullscreenElement","exitFullscreen","webkitExitFullscreen","expandIcon","compressIcon","requestFullscreen","webkitRequestFullscreen","handleFullscreenChange","isFullscreen","setTourControlsVisible","visible","progressText","progressContainer","scrollButtons","setExploreControlsVisible","exploreControls","setCameraMode","modeButtonContainer","modeButtons","querySelectorAll","forEach","btn","mode","getAttribute","b","btnMode","lastProgressUpdate","lastDisplayedPercentage","progress","percentage","max","min","roundedPercentage","round","now","performance","progressBar","width","String","lastDisplayedWaypointIndex","index","waypoint","waypointInfo","titleEl","descEl","getWaypoints","showHotspotPopup","hotspot","popup","contentEl","closeBtn","cssText","activationMode","hasMediaContent","photoUrl","popupVideoUrl","contentType","iframeUrl","bgAlpha","backgroundAlpha","backgroundColor","hex","r","parseInt","substring","g","textColor","color","fontFamily","fontSize","closeButtonColor","contentHtml","information","externalLinkUrl","btnColor","externalLinkButtonColor","externalLinkText","setJoystickVisible","joystick","joystickThumb","transform","lookZone","updateJoystickPosition","active","dx","dy","maxRadius","distance","sqrt","clampedDistance","clampedX","clampedY","updateLookZoneState","updateWaypointListActive","waypointListContainer","item","tmpV1","pc","Vec3","tmpV2","pose","Pose","frame","InputFrame","move","rotate","applyDeadZone","stick","low","high","mag","fill","screenToWorld","camera","dz","out","aspectRatio","horizontalFov","projection","orthoHeight","app","system","height","graphicsDevice","clientRect","set","halfSize","PROJECTION_PERSPECTIVE","halfSlice","tan","math","DEG_TO_RAD","mul","CameraControls","constructor","config","this","enabled","_mode","_enableOrbit","_enableFly","enablePan","_pose","_preFocusMode","_startZoomDist","_pitchRange","Vec2","_yawRange","Infinity","_lastFocusPoint","_zoomRange","_state","axis","shift","ctrl","mouse","touches","moveSpeed","moveFastSpeed","moveSlowSpeed","rotateSpeed","rotateTouchSens","rotateJoystickSens","zoomSpeed","zoomPinchSens","keyboardSpeedMultiplier","gamepadDeadZone","invertRotation","joystickEventName","_collisionEntities","_collisionRadius","_prevPosition","_prevPositionValid","_collisionTestVec","_destroyHandler","cameraComponent","_flyController","FlyController","_orbitController","OrbitController","_focusController","FocusController","moveDamping","rotateDamping","zoomDamping","zoomRange","canvas","_desktopInput","KeyboardMouseSource","_orbitMobileInput","MultiTouchSource","_flyMobileInput","DualGestureSource","_gamepadInput","GamepadSource","attach","bx","by","sx","sy","fire","look","getPosition","ZERO","_setMode","_controller","enableOrbit","enableFly","focusPoint","focusDamping","pitchRange","yawRange","mobileInputLayout","enable","damping","point","getFocus","range","copy","clamp","layout","previousMode","detach","_lastYaw","angles","setMode","focus","resetZoom","zoomDist","forward","mulScalar","flyTo","targetPoint","stopDistance","cameraPos","totalDist","actualStopDist","direction","sub","normalize","reset","syncFromCamera","target","clone","focusDistance","yaw","eulerAngles","getEulerAngles","syncFromPose","focusTarget","setPosition","setRotation","tempEntity","Entity","disable","read","update","dt","keyCode","button","wheel","touch","pinch","count","leftInput","rightInput","leftStick","rightStick","D","A","RIGHT","LEFT","E","Q","W","S","UP","DOWN","SHIFT","CTRL","double","desktopPan","mobileJoystick","endsWith","moveMult","zoomMult","zoomTouchMult","rotateMult","rotateTouchMult","rotateJoystickMult","deltas","v","keyMove","panMove","wheelMove","append","mouseRotate","flyMove","orbitMove","pinchMove","orbitRotate","flyRotate","stickMove","stickRotate","xr","focusInterrupt","focusComplete","complete","newPos","prev","corrected","checkCollision","yawDelta","setEulerAngles","setCollisionEntities","entities","radius","entity","entityPos","entityScale","getLocalScale","meshType","_collisionMeshType","halfWidth","halfHeight","halfDepth","centerX","centerY","centerZ","customBounds","_collisionBounds","center","halfExtents","abs","destroy","toXYZ","fallback","CharacterController","velocity","isGrounded","pitch","keys","mouseLocked","sprintMultiplier","lookSensitivity","gravity","maxFallSpeed","jumpVelocity","collisionRadius","stepHeight","groundCheckDistance","horizontalVelocity","targetVelocity","collisionEntities","floorEntity","keydownHandler","keyupHandler","mousemoveHandler","clickHandler","pointerlockchangeHandler","tmpVec","tmpVec2","right","createCollisionMeshes","loadPromises","customMeshUrl","promise","loadCustomCollisionMesh","push","addComponent","configureCollisionEntity","all","ext","assetType","asset","Asset","reject","ready","containerResource","resource","instantiateRenderEntity","renderEntity","children","addChild","computeAndStoreBounds","err","error","assets","load","bounds","BoundingBox","boundsInitialized","traverse","node","render","meshInstances","mi","aabb","child","pos","rot","scaling","setLocalScale","setEntityVisibility","root","setupInputHandlers","removeInputHandlers","pointerLockElement","exitPointerLock","e","code","movementX","movementY","requestPointerLock","removeEventListener","checkGround","floorPos","floorScale","highestGround","topY","moveX","moveZ","isSprinting","yawRad","sin","cos","speed","lerpFactor","pow","damp","lerp","currentPos","groundY","targetFeetY","collisionMeshEntities","grounded","getVelocity","_GsplatRevealRadialClass","getGsplatRevealRadialClass","GsplatRevealRadial","createScript","Object","assign","prototype","effectTime","_materialsApplied","_shadersApplied","_retryCount","_maxRetries","_materialCreatedHandler","_systemMaterialHandler","_centerArray","_dotTintArray","_waveTintArray","acceleration","delay","dotTint","waveTint","oscillationIntensity","endRadius","initialize","Set","Color","_applyShaders","_removeShaders","floor","toFixed","size","_isEffectComplete","_updateUniforms","_setUniform","_getCompletionTime","liftStartTime","discriminant","getShaderGLSL","getShaderWGSL","gsplatComponent","gsplat","gsplatRuntime","isUnified","unified","gsplatSystem","systems","material","layer","_applyShaderToMaterial","cameras","findComponents","layers","cameraComp","layerId","getLayerById","getGSplatMaterial","instance","_instance","_applyToInstance","layerList","miRuntime","gsplatInstance","_gsplatInstance","gsplatInst","checkEntity","runtimeEntity","inst","materials","_materials","materialsAsMC","Map","_material","has","methods","obj","names","getOwnPropertyNames","_error","getPrototypeOf","m","startsWith","join","glsl","wgsl","materialWithShaders","hasSetShaderChunk","setShaderChunk","chunks","gsplatEffectGLSL","gsplatEffectWGSL","shader","setParameter","clear","off","REVEAL_PRESETS","fast","medium","slow","getRevealPreset","preset","defineProperty","lib","loop","conditional","parse","stream","schema","result","arguments","parent","partSchema","conditionFunc","continueFunc","arr","lastStreamPos","newParent","uint8","readBits","readArray","readString","peekBytes","readBytes","readByte","buildStream","uint8Data","peekByte","offset","subarray","from","fromCharCode","readUnsigned","littleEndian","bytes","byteSize","totalOrFunc","total","parser","_byte","bits","reduce","res","def","startIndex","subBitsTotal","decompressFrames","decompressFrame","parseGIF","_gif","exports","_","require$$0","_uint","require$$1","subBlocksSchema","blocks","streamSize","availableSize","Uint8Array","gceSchema","gce","codes","extras","future","disposal","userInput","transparentColorGiven","transparentColorIndex","terminator","imageSchema","image","descriptor","left","top","lct","exists","interlaced","sort","minCodeSize","textSchema","blockSize","preData","applicationSchema","application","commentSchema","comment","_default","header","signature","version","lsd","gct","resolution","backgroundColorIndex","pixelAspectRatio","frames","nextCode","__esModule","default","_jsBinarySchemaParser","require$$2","_deinterlace","deinterlace_1","deinterlace","pixels","newPixels","rows","cpRow","toRow","fromRow","fromPixels","slice","splice","apply","concat","offsets","steps","pass","_lzw","lzw_1","lzw","pixelCount","available","code_mask","code_size","end_of_information","in_code","old_code","data_size","datum","first","pi","bi","MAX_STACK_SIZE","npix","dstPixels","prefix","suffix","pixelStack","arrayBuffer","byteData","buildImagePatch","totalPixels","resultImage","dims","colorTable","disposalType","transparentIndex","patch","patchData","Uint8ClampedArray","colorIndex","generatePatch","parsedGif","buildImagePatches","f","AnimatedGifTexture","currentFrameIndex","isLoaded","lastFrameTime","updateHandler","texture","gifWidth","gifHeight","drawFrame","ctx","getContext","willReadFrequently","buffer","gif","Texture","format","PIXELFORMAT_RGBA8","mipmaps","minFilter","FILTER_LINEAR","magFilter","addressU","ADDRESS_CLAMP_TO_EDGE","addressV","onReady","onError","frameIndex","prevFrame","clearRect","imageData","ImageData","tempCanvas","putImageData","drawImage","updateTexture","getImageData","lock","unlock","upload","stop","playing","loaded","HtmlMeshManager","meshes","useTexElement2D","GraphicsDevice","_isHTMLElementInterface","HTMLImageElement","HTMLCanvasElement","HTMLVideoElement","originalIsBrowserInterface","_isBrowserInterface","call","patchPlayCanvasForHtmlMesh","device","supportsTexElement2D","createMesh","htmlElement","createHtmlElement","createTexture","createMaterial","createEntity","destroyMesh","updateMeshTexture","animated","startUpdateLoop","pointerEvents","zIndex","overflow","css","html","setAttribute","visibility","body","setSource","renderToCanvas","svg","img","Image","blob","Blob","URL","createObjectURL","revokeObjectURL","isTainted","toDataURL","fctx","fillStyle","fillRect","font","textAlign","fillText","onerror","StandardMaterial","diffuseMap","emissiveMap","emissive","opacity","blendType","BLEND_NORMAL","BLEND_NONE","cull","doubleSided","CULLFACE_NONE","CULLFACE_BACK","aspect","castShadows","receiveShadows","billboard","findComponent","lookAt","w","h","lastUpdate","rate","updateRate","last","delete","values","some","getMesh","updateVisibility","scrollPercent","waypointIndex","visibilityRange","start","end","billboardRange","billboardActive","_billboardActive","getAllMeshes","setupHtmlMeshes","manager","CustomScriptSystem","isInitialized","scriptCleanup","lastError","updateCallbacks","api","registerCleanup","fn","addCleanup","updateScript","execute","sanitizeScript","s","preprocessScript","processed","trim","cleanup","processedScript","cancelled","watchdog","requestAnimationFrame","wrappedApp","create","registerUpdate","callback","safeCallback","idx","indexOf","registerBeforeRender","pcNamespace","getScrollPercentage","getCurrentWaypointIndex","getHotspots","getSplats","getHTMLMeshes","func","Function","module","fakeRequire","blocked","Proxy","cleanupCandidate","bind","getLastError","dispose","FrameSequencePlayer","frameAssets","activeEntityIndex","currentFrame","loadingFrames","destroyed","isDisplaying","listeners","frameUrls","fps","preloadCount","frameInterval","createSplatEntity","preloadInitialFrames","autoplay","preloadFrame","then","framesToLoad","onLoadProgress","unloadFrame","unload","updatePreloadWindow","displayFrame","nextEntityIdx","currentEntity","nextEntity","emit","onFrameChange","elapsed","nextFrame","setFrame","previousFrame","getCurrentFrame","getTotalFrames","getProgress","setProgress","getFps","setFps","getIsPlaying","setLoop","getLoop","setScale","event","args","cb","EventEmitter","isMobileDevice","vendor","window","opera","LOD_PRESETS","lodDistances","desktop","mobile","isLodStreamingFormat","isLod","createViewer","lazyEvents","pendingButtonLabels","actualInstance","lazyLoadThumbnail","buttonText","thumbnailType","onStart","lazyLoadContainer","mediaType","lowerUrl","detectMediaType","startBtn","video","transition","createLazyLoadUI","setButtonLabels","goToWaypoint","getWaypointCount","getRotation","getCameraMode","setExploreMode","goToOriginalSplat","goToSplat","getCurrentSplatUrl","isShowingOriginalSplat","getAdditionalSplats","muteAll","unmuteAll","isMuted","triggerHotspot","closeHotspot","el","resize","navigateToScene","events","allowParentStyles","originalWidth","originalHeight","message","errorMessage","isOutdatedScene","showErrorPopup","isEditorMode","editor","showUI","editorSkipUI","uiOpts","currentButtonLabels","mapCameraMode","hasCollisionMeshesForWalk","allowedModes","a","defaultMode","sceneHasAudio","audioUrl","videoMuted","uiElements","showScrollControls","showModeToggle","showFullscreenButton","showHelpButton","showMuteButton","showPreloader","modeContainer","fullscreenBtn","muteBtn","unmutedIcon","createElementNS","unmutedPath","mutedIcon","mutedPath","muteButton","waypointItemsHtml","toggleBtn","dropdown","stopPropagation","contains","vrBtn","vrButton","arBtn","arButton","helpBtn","hotspotPopup","portalPopup","watermark","finalLink","fpsCounter","createUIElements","baseGraphicsOptions","antialias","alpha","powerPreference","Application","graphicsDeviceOptions","Mouse","TouchDevice","keyboard","Keyboard","webgl2Error","preferWebGl2","webgl1Error","errorDiv","heading","preventDefault","setCanvasFillMode","FILLMODE_FILL_WINDOW","setCanvasResolution","RESOLUTION_AUTO","worldLayer","getLayerByName","LayerProto","_splitLightsPatched","origDesc","getOwnPropertyDescriptor","origGetter","_splitLightsDirty","_lights","l","_type","configurable","isMobile","lodPresetName","lodPreset","lodUpdateAngle","lodBehindPenalty","radialSorting","lodUpdateDistance","lodUnderfillLimit","lodRangeMin","lodRangeMax","colorUpdateDistance","colorUpdateAngle","colorUpdateDistanceLodScale","colorUpdateAngleLodScale","currentWaypointIndex","splatEntity","revealScript","isDestroyed","frameSequencePlayer","currentSplatUrl","isLoadingSplat","preloadedSplats","lastSplatCheckProgress","lastSplatCheckWaypointIndex","clearColor","finalRot","convertWaypointRotation","light","intensity","baseMoveSpeed","cameraControls","characterController","catch","currentCameraMode","waypointControlEnabled","picker","Picker","isInXR","xrSessionType","arContentEntity","arContentTexture","arContentVisible","hideARContent","fpsUpdateTimer","hotspotEntities","hotspotData","mediaTriggerMode","hotspotPos","isInProximity","proximityDistance","wasInProximity","videoElement","playVideoHotspot","audioElements","audio","paused","audioCtx","state","resume","pauseVideoHotspot","updateProximityTriggers","waypointAudioMap","audioData","audioId","slotId","assetReady","slot","sound","spatialSound","audioPos","maxDistance","stopOnExit","updateWaypointAudioProximity","audioEmitterMap","emitterData","emitterPos","updateAudioEmitterProximity","wpPos","triggerDist","activeWaypointTriggers","interaction","executeWaypointInteractions","reverseWaypointInteractions","checkWaypointTriggerDistance","panelPos","rotateLocal","updateARContentPosition","exploreBtns","updateExploreBtnState","activeMode","userYawOffset","userPitchOffset","isUserDragging","exploreDefault","targetCameraPosition","targetCameraRotation","pickerScale","canvasEl","clientWidth","clientHeight","prepare","worldPoint","getWorldPointAsync","findBetterFocusPoint","updateProgress","loadingLabel","bar","textEl","percent","updatePreloaderProgress","isStorySplatHostedUrl","host","qx","_x","qy","_y","qz","_z","qw","_w","Quat","setFromEulerAngles","hideSplat","disposeSplat","showSplat","preloadSplat","Date","invertX","invertY","finalScale","finalRotDeg","applyExploreModeForSplat","splatExploreMode","isOriginal","targetMode","loadSwapSplat","swapIndex","findIndex","currentIndex","nextIndex","nextSplat","preloadNextSplat","getPrimarySplatUrl","updateSplats","numWaypoints","currentProgress","bestWaypointSplat","bestPercentageSplat","bestWaypointTrigger","bestPercentageTrigger","splat","bestSplat","isReturnToOriginal","primaryUrl","targetUrl","defaultExploreMode","skyboxAsset","TEXTURETYPE_RGBM","skyboxMip","rotQuat","applySkybox","targetProgress","isAnimatingProgress","totalDuration","sum","pathLength","playbackSpeed","rawLoopMode","playbackDirection","PULL_STRENGTH","lastPointerX","lastPointerY","waypointPositions","waypointRotations","waypointFOVs","defaultFOV","targetFOV","updateCameraFromProgress","segmentProgress","segmentIndex","t","startPos","endPos","startRot","endRot","slerp","startFOV","endFOV","newIndex","prevIndex","currentWaypoint","waypointCameraMode","orbitTarget","previousIndex","autoplayTriggered","isLeaving","stopAllHotspotAudio","stopHotspotPopupMedia","popupEl","animate","clampedProgress","animateToProgress","progressDiff","newFOV","userRotationOffset","targetWithUserOffset","mul2","currentRot","newRot","transitionDuration","animationTarget","startProgress","startTime","waypointProgress","buttonMode","scrollAmountSetting","newProgress","lastPlaybackTime","playbackAnimationId","playbackLoop","timestamp","deltaTime","progressIncrement","cancelAnimationFrame","scrollCanvas","scrollSpeedSetting","effectivePathLength","baseIncrement","deltaY","scrollIncrement","passive","clientX","clientY","capture","deltaX","portalEntities","portalPopupEl","pendingPortal","hidePortalPopup","confirmBtn","cancelBtn","portal","handlePortalNavigation","parseColor","allAudioContexts","allAudioElements","globalMuted","storedVolumes","currentTime","containerEl","iframe","particleEntities","particleTextures","PARTICLE_TEXTURE_URLS","flare","circle","spark","rain","smoke","loadParticleTexture","createParticleSystemEntity","psConfig","getPos","vec","defaultVal","getColor","emitterPosition","color1","color2","colorDead","direction1","direction2","lifetime","minLifeTime","maxLifeTime","emitterShape","EMITTERSHAPE_BOX","emitterExtents","emitterOffset","emitterType","EMITTERSHAPE_SPHERE","emitterRadius","emitBoxMin","emitBoxMax","boxMin","boxMax","blendMode","BLEND_ADDITIVEALPHA","blendModeStr","BLEND_MULTIPLICATIVE","BLEND_ADDITIVE","gravityX","gravityY","gravityZ","endVelX","endVelY","endVelZ","minAngularSpeed","maxAngularSpeed","angularSpeed","minInitialRotation","maxInitialRotation","minSize","maxSize","minScaleX","maxScaleX","minScaleY","maxScaleY","minEmitPower","maxEmitPower","avgDirX","avgDirY","avgDirZ","numParticles","emitRate","startAngle","startAngle2","radialSpeedGraph","Curve","localVelocityGraph","CurveSet","localVelocityGraph2","velocityGraph","scaleGraph","scaleGraph2","rotationSpeedGraph","rotationSpeedGraph2","colorGraph","alphaGraph","blend","depthWrite","depthSoftening","softParticles","lighting","halfLambert","alignToMotion","stretch","preWarm","orientation","particlesystem","localSpace","translationPivotX","translationPivot","translationPivotY","renderingGroupId","drawOrder","customMeshEntities","playAnimComponentV2","animInfo","component","animations","clips","modelEntity","animList","anim","activate","animComp","animAsset","animTrack","animName","_name","assignAnimation","assignErr","animComponent","baseLayer","playableState","states","pauseAnimComponentV2","loadCustomMesh","meshConfig","modelUrl","bjsRotationToPC","modelAsset","meshId","meshData","isAnimPlaying","audioPlaying","enableAllChildren","childCount","tameGlbMaterials","mat","specular","gloss","opacityMode","applyOpacityToCustomMesh","originalRotation","camPos","meshPos","angle","atan2","hasGlbAnimations","hasAnimations","animationCount","interactionConfig","playModelAnimation","allAnimComponents","findAllAnimComponents","depth","animation","animationNames","shouldAutoPlay","animationAutoPlay","playAudio","audioConfig","positional","audioSpatial","distanceModel","audioDistanceModel","refDistance","audioRefDistance","audioMaxDistance","rollOffFactor","audioRolloffFactor","slots","audioLoop","volume","audioVolume","audioSlotId","audioAsset","setupMeshAudio","customMesh","triggerUIPopup","triggerDirectLink","hasInteractions","canvasForMesh","performRaycast","rect","getBoundingClientRect","to","dir","sub2","rayIntersectsMeshHierarchy","dot","add2","popupTriggerMode","audioTriggerMode","animationTriggerMode","directLinkTriggerMode","isHovering","handleHoverIn","cursor","displayMeshContent","handleDirectLink","handleHoverOut","meshPopup","hideMeshContent","handleClick","meshClickHandler","meshHoverHandler","nowHovering","meshLeaveHandler","setupMeshClickInteraction","opacityConfig","rayOrigin","rayDir","rayIntersectsAABB","model","getMin","getMax","tmin","tmax","axes","readAxis","byAxis","byUnderscore","origin","minVal","maxVal","t1","t2","existingPopup","popupContent","content","onclick","showMeshPopup","directLinkUrl","open","applyToEntity","ent","meshInstance","_isCloned","cloned","BLEND_PREMULTIPLIED","depthTest","alphaTest","cleanupCustomMeshes","canvasForCleanup","opacityAnimation","startPercent","endPercent","startOpacity","endOpacity","bRange","updateCustomMeshVisibility","currentSkyboxEntity","skyboxFollowCameraHandler","initSkybox","skyboxIntensity","enableIBL","isHDR","skyboxEntity","skyboxMaterial","useLighting","textureAsset","FILTER_LINEAR_MIPMAP_LINEAR","exposure","toneMapping","TONEMAP_ACES","ambientLight","iblError","rotationDegrees","lightEntities","hexToColorForLights","exec","createPointLight","lightConfig","createDirectionalLightEntity","createHemisphericLight","groundColor","skyColor","createAmbientLight","createSpotLight","radToDeg","angleDeg","exponent","innerAngleDeg","innerConeAngle","innerAngle","outerAngleDeg","outerConeAngle","outerAngle","shadowBias","normalOffsetBias","dirVec","getPlayCanvasDistanceModel","modelStr","muteAllAudio","_storedVolume","muted","unmuteAllAudio","storedVol","animatedGifs","createImageMaterial","imageUrl","onTextureReady","twoSidedLighting","diffuse","lower","isGifUrl","gifTexture","premultiplyAlpha","opacityMap","opacityMapChannel","crossOrigin","rx","ry","rz","hy","hx","hz","cy","cx","cz","sz","applyPlaneRotation","rxRad","ryRad","rzRad","faceForward","userRot","createSingleHotspot","rawScale","scaleObj","rotXRad","rotYRad","rotZRad","sphereOpacity","initialOpacity","targetOpacity","textureLoaded","hiddenUntilTextureLoaded","shouldBeVisible","hotspotMaterial","videoUrl","useAlphaMethod","useIOSVideoAlphaMethod","forceIOSVideoAlphaMethodForAllDevices","mainVideoUrl","iosMainVideoUrl","alphaVideoUrl","alphaMaskVideoUrl","hasWebMAlpha","isWebMUrl","webmHasAlpha","createVideoAndTexture","isAlpha","useRGBA","videoLoop","playsInline","preload","PIXELFORMAT_R8_G8_B8_A8","PIXELFORMAT_R8_G8_B8","main","videoTexture","videoBackupUrl","backupUrl","alphaVideo","alphaTexture","readyState","HAVE_ENOUGH_DATA","initialScale","videoWidth","videoHeight","ratio","alphaVideoElement","isVideoPlaying","once","videoSpatialAudio","AudioContext","webkitAudioContext","source","createMediaElementSource","panner","createPanner","panningModel","videoDistanceModel","videoRefDistance","videoMaxDistance","rolloffFactor","videoRolloffFactor","connect","destination","camForward","camUp","up","listener","positionX","positionY","positionZ","forwardX","forwardY","forwardZ","upX","upY","upZ","setOrientation","setupVideoSpatialAudio","overlayEntity","setLocalPosition","overlayCanvas","octx","beginPath","moveTo","lineTo","closePath","textBaseline","shadowColor","shadowBlur","shadowOffsetX","shadowOffsetY","overlayText","overlayTexture","overlayMat","updateOverlayVisibility","videoOverlay","gifUrl","loadAnimatedGif","gifCanvas","animateGif","gifAnimationFrame","fallbackMaterial","updateAudioPosition","setupHotspotAudio","hasBillboardRange","billboardRangeStart","billboardRangeEnd","_billboardOriginalRotation","updateHotspotVisibility","alwaysVisible","rangeStart","rangeEnd","origRot","triggerMode","createSinglePortal","rawPortalScale","portalScaleObj","portalMaterial","targetSceneName","HAVE_CURRENT_DATA","portalData","targetSceneId","updatePortalVisibility","portalPos","proximityTriggered","raycastPortals","closestHit","portalId","sceneName","existing","getComputedStyle","loadingDiv","spinner","inset","background","flexDirection","alignItems","justifyContent","border","borderTop","borderRadius","marginBottom","styleEl","showPortalLoadingUI","sceneApiUrl","status","newSceneData","updatePortalLoadingText","portalHtmlMgr","__htmlMeshManager","portalScriptSys","__customScriptSystem","cleanupForPortalNavigation","parentNode","hidePortalLoadingUI","padding","clickSuppressed","pointerDown","pointerStartX","pointerStartY","clearPointerDown","raycastHotspots","currentHoverHotspot","isMouseOverPopup","overlay","handleDoubleClickFocus","screenX","screenY","hotspotHit","scaledX","scaledY","getSelectionAsync","aabbCenter","portalHit","effectiveActivationMode","hit","overlayEl","confirmNavigation","showPortalPopup","hitResult","hasPopupContent","canvasWidth","canvasHeight","bgColor","strokeStyle","lineWidth","strokeRect","currentY","maxWidth","lineHeight","words","line","word","testLine","measureText","wrapText","showARContent","teleportWaypoint","teleportToWaypoint","teleportPercent","teleportToPercent","teleportMode","calculatedTargetProgress","targetIndex","lastTapTime","changedTouches","loadContent","bandwidthUsed","isStorySplatHosted","loadedUrl","urls","isLodFormat","urlIsSogFormat","extension","isLodStreaming","isSogFormat","received","loadProgress","hasRejected","unhandledRejectionHandler","errorMsg","reason","details","gs","distances","userRotation","revealPresetName","revealEffect","revealConfig","GsplatRevealRadialClass","syncError","errObj","statusCode","handler","loader","getHandler","octreeParser","parsers","octree","urlObj","isHostedOnStorySplat","lodEnabled","bandwidthCounted","audioEntity","soundConfig","audioDataRef","emitters","emitter","random","toString","substr","addSlot","overlap","setupAudioEmitters","ps","textureName","particleTexture","textureUrl","textureCacheKey","customTextureUrl","colorMap","safeName","stack","initParticleSystems","loadedCount","skippedCount","hasModelUrl","initCustomMeshes","isAvailable","XRTYPE_VR","XRTYPE_AR","startXr","XRSPACE_LOCALFLOOR","setupXR","htmlMeshManager","customScriptSystem","scriptSystem","setupCustomScript","urlParams","URLSearchParams","location","search","waypointParam","autoplayParam","isNaN","popupCloseBtn","onWaypointClick","items","setupWaypointListClickHandlers","handleResize","resizeCanvas","editorHotspotMap","editorLightMap","editorParticleMap","editorCustomMeshMap","editorPortalMap","getFrameProgress","setFrameProgress","splatConfig","currentUrl","destroyScriptSys","destroyHtmlMgr","gl","modeBtn","orbitBtn","flyBtn","wpToggle","fsBtn","addEl","tag","bold","addSpacer","editorInstance","getCameraControls","getApp","getSplatEntity","getAllHotspotEntities","getAllLightEntities","getCamera","addHotspot","removeHotspot","updateHotspot","addLight","lightCfg","removeLight","updateLight","addCustomMesh","meshCfg","removeCustomMesh","addParticleSystem","removeParticleSystem","addPortal","removePortal","videoEl","updatePortal","addHtmlMesh","htmlContent","removeHtmlMesh","addCollisionMesh","px","py","pz","renderType","_collisionMeshId","removeCollisionMesh","addAudioEmitter","emitterConfig","removeAudioEmitter","updateAudioEmitter","setSkybox","setSkyboxRuntime","setBackgroundColor","pcColor","setFOV","newFov","addWaypoint","removeWaypoint","updateWaypoint","rebuildTourPath","createViewerFromUrl","trackEmbedUsage","baseUrl","ownerId","method","headers","SceneNotFoundError","super","SceneApiError","DEFAULT_BASE_URL","process","env","STORYSPLAT_API_URL","browserWindow","__STORYSPLAT_API_URL__","getDefaultBaseUrl","createViewerFromSceneId","apiUrl","encodeURIComponent","apiKey","errorText","apiResponse","success","meta","_baseUrl","_apiKey","viewerOptions","bandwidthTracked","fetchSceneMeta","userSlug","GizmoManager","translateGizmo","rotateGizmo","scaleGizmo","activeGizmo","attachedEntity","gizmoLayer","Gizmo","createLayer","TranslateGizmo","RotateGizmo","ScaleGizmo","setSnap","snap","snapIncrement","setCoordSpace","coordSpace","setupEvents","gizmo","onTransformStart","onTransformMove","onTransformEnd","attachToMesh","mesh","positionGizmoEnabled","rotationGizmoEnabled","scaleGizmoEnabled","increment","space","onTransform","callbacks","SelectionManager","selectedEntities","highlightedEntity","originalMaterials","highlightColor","multiSelect","pickAtScreenPosition","pickerWidth","pickerHeight","pickerX","pickerY","selection","getSelection","tags","getWorldPointAtScreen","select","additive","previousEntity","getSelectedEntity","deselectAll","applyHighlight","onSelectionChange","deselect","removeHighlight","isSelected","getSelectedEntities","materialMap","highlightMaterial","emissiveIntensity","originalMaterial","onSelect","handlePointerDown","MouseEvent","EditorCameraController","tourCamera","fpvEntity","_enabled","editCamera","tourCam","showFPVVisualization","createFPVVisualization","tourPos","tourRot","focusOn","dist","orbitDistance","syncClearColor"],"mappings":"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","x_google_ignoreList":[7,8,9,10,11,12]}
|