Spaces:
				
			
			
	
			
			
					
		Running
		
	
	
	
			
			
	
	
	
	
		
		
					
		Running
		
	| (function() { | |
| let cameraInstance = null; | |
| let controlsInstance = null; | |
| let initialCameraPosition = null; | |
| let initialCameraRotation = null; | |
| function getScriptQueryParam(param) { | |
| var params = new URLSearchParams(""); | |
| if (document.currentScript && document.currentScript.src.indexOf('?') !== -1) { | |
| var queryString = document.currentScript.src.split('?')[1]; | |
| params = new URLSearchParams(queryString); | |
| } else { | |
| params = new URLSearchParams(window.location.search); | |
| } | |
| return params.get(param); | |
| } | |
| var plyUrl = getScriptQueryParam("ply_url"); | |
| var minZoom = parseFloat(getScriptQueryParam("minZoom") || "0"); | |
| var maxZoom = parseFloat(getScriptQueryParam("maxZoom") || "20"); | |
| var minAngle = parseFloat(getScriptQueryParam("minAngle") || "0"); | |
| var maxAngle = parseFloat(getScriptQueryParam("maxAngle") || "360"); | |
| var isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; | |
| var styleEl = document.createElement('style'); | |
| styleEl.textContent = ` | |
| #viewer-container { | |
| position: relative; | |
| width: 100%; | |
| height: 100vh; | |
| background: #FEFEFD; | |
| border: 1px solid #474558; | |
| border-radius: 10px; | |
| } | |
| #canvas { | |
| width: 100%; | |
| height: 100%; | |
| display: block; | |
| } | |
| #progress-dialog { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background: rgba(255,255,255,0.9); | |
| padding: 20px; | |
| border-radius: 5px; | |
| z-index: 1000; | |
| display: none; | |
| } | |
| #menu-content { | |
| display: none; | |
| position: absolute; | |
| top: 62px; | |
| right: 70px; | |
| background: #FFFEF4; | |
| border: 1px solid #474558; | |
| border-radius: 5px; | |
| padding: 10px; | |
| font-size: 15px; | |
| line-height: 1.4; | |
| color: #474558; | |
| } | |
| .widget-button { | |
| position: absolute; | |
| width: 45px; | |
| height: 45px; | |
| background-color: #FFFEF4; | |
| border: 1px solid #474558; | |
| border-radius: 50%; | |
| cursor: pointer; | |
| font-size: 14px; | |
| color: #474558; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| #fullscreen-toggle { top: 17px; right: 15px; } | |
| #help-toggle { top: 72px; right: 15px; } | |
| #reset-camera-btn { top: 127px; right: 15px; } | |
| `; | |
| document.head.appendChild(styleEl); | |
| var viewerContainer = document.createElement('div'); | |
| viewerContainer.id = 'viewer-container'; | |
| viewerContainer.innerHTML = ` | |
| <canvas id="canvas"></canvas> | |
| <div id="progress-dialog"> | |
| <progress id="progress-indicator" max="100" value="0"></progress> | |
| </div> | |
| <button id="fullscreen-toggle" class="widget-button">⇱</button> | |
| <button id="help-toggle" class="widget-button">?</button> | |
| <button id="reset-camera-btn" class="widget-button">🗘</button> | |
| <div id="menu-content"> | |
| - Rotate with right click<br> | |
| - Zoom in/out with middle click<br> | |
| - Translate with left click | |
| </div> | |
| `; | |
| document.currentScript.parentNode.appendChild(viewerContainer); | |
| var fullscreenToggle = document.getElementById('fullscreen-toggle'); | |
| var helpToggle = document.getElementById('help-toggle'); | |
| var resetCameraBtn = document.getElementById('reset-camera-btn'); | |
| var menuContent = document.getElementById('menu-content'); | |
| var canvas = document.getElementById('canvas'); | |
| var progressDialog = document.getElementById('progress-dialog'); | |
| var progressIndicator = document.getElementById('progress-indicator'); | |
| fullscreenToggle.addEventListener('click', function() { | |
| if (isIOS) { | |
| viewerContainer.classList.toggle('fake-fullscreen'); | |
| fullscreenToggle.textContent = viewerContainer.classList.contains('fake-fullscreen') ? '⇲' : '⇱'; | |
| } else { | |
| if (!document.fullscreenElement) { | |
| viewerContainer.requestFullscreen(); | |
| } else { | |
| document.exitFullscreen(); | |
| } | |
| } | |
| }); | |
| document.addEventListener('fullscreenchange', function() { | |
| fullscreenToggle.textContent = document.fullscreenElement ? '⇲' : '⇱'; | |
| }); | |
| helpToggle.addEventListener('click', function(e) { | |
| e.stopPropagation(); | |
| menuContent.style.display = menuContent.style.display === 'block' ? 'none' : 'block'; | |
| }); | |
| resetCameraBtn.addEventListener('click', function() { | |
| if (cameraInstance && initialCameraPosition && initialCameraRotation) { | |
| cameraInstance.position.copy(initialCameraPosition); | |
| cameraInstance.rotation.copy(initialCameraRotation); | |
| if (typeof cameraInstance.update === 'function') cameraInstance.update(); | |
| if (controlsInstance && typeof controlsInstance.update === 'function') controlsInstance.update(); | |
| } | |
| }); | |
| async function initializeViewer() { | |
| const SPLAT = await import("https://cdn.jsdelivr.net/npm/gsplat@latest"); | |
| progressDialog.style.display = 'block'; | |
| const renderer = new SPLAT.WebGLRenderer(canvas); | |
| const scene = new SPLAT.Scene(); | |
| const camera = new SPLAT.Camera(); | |
| const controls = new SPLAT.OrbitControls(camera, canvas); | |
| cameraInstance = camera; | |
| controlsInstance = controls; | |
| initialCameraPosition = camera.position.clone(); | |
| initialCameraRotation = camera.rotation.clone(); | |
| canvas.style.background = "#FEFEFD"; | |
| controls.maxZoom = maxZoom; | |
| controls.minZoom = minZoom; | |
| controls.minAngle = minAngle; | |
| controls.maxAngle = maxAngle; | |
| controls.update(); | |
| try { | |
| await SPLAT.PLYLoader.LoadAsync(plyUrl, scene, (progress) => { | |
| progressIndicator.value = progress * 100; | |
| }); | |
| progressDialog.style.display = 'none'; | |
| } catch (error) { | |
| console.error("Error loading PLY file:", error); | |
| progressDialog.innerHTML = `<p style="color: red">Error loading model: ${error.message}</p>`; | |
| } | |
| const frame = () => { | |
| controls.update(); | |
| renderer.render(scene, camera); | |
| requestAnimationFrame(frame); | |
| }; | |
| window.addEventListener("resize", () => renderer.setSize(canvas.clientWidth, canvas.clientHeight)); | |
| requestAnimationFrame(frame); | |
| } | |
| initializeViewer(); | |
| })(); | |