Spaces:
Running
Running
Create a photorealistic 3D Earth globe visualization with the following specifications: Visual Requirements: - Ultra-realistic Earth rendering that fills 90% of the screen - High-resolution satellite imagery texture showing actual Earth surface details - Accurate continental landmasses with visible topography (mountains, valleys, deserts) - Realistic ocean colors with depth variations (deep blue to turquoise near shores) - Dynamic cloud layer with semi-transparent, moving cloud formations - Atmospheric glow around the edges (thin blue atmosphere layer) - Subtle city lights visible on the night side of Earth - Realistic lighting with sun illumination from one side creating day/night terminator Interactive Features: - Smooth rotation animation (0.2 degrees per frame) - Mouse/touch controlled rotation in all directions - Zoom capability (scroll wheel) from full globe view to continent level - Click on cities to highlight with red pulsing markers - City labels appear on hover (white text with black outline) - Smooth camera transitions when zooming to specific locations Technical Details: - WebGL-based rendering for smooth performance - Sphere geometry with 128x64 segments for smoothness - Multiple texture layers: base map (8K resolution), cloud layer, night lights - Specular mapping for ocean reflections - Normal mapping for terrain elevation - Real-time shadows on cloud layer Style Reference: Similar to Google Earth's photorealism but optimized for web performance. The globe should look like you're viewing Earth from space through a high-quality telescope - every detail crisp and believable, not stylized or cartoon-like. - Initial Deployment
364fb61
verified
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Photorealistic Earth Globe</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.min.js"></script> | |
| <style> | |
| body, html { | |
| margin: 0; | |
| padding: 0; | |
| overflow: hidden; | |
| height: 100%; | |
| background: linear-gradient(to bottom, #000428, #004e92); | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| } | |
| #globe-container { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| .info-panel { | |
| position: absolute; | |
| bottom: 20px; | |
| left: 20px; | |
| background: rgba(0, 0, 30, 0.7); | |
| color: white; | |
| padding: 15px; | |
| border-radius: 10px; | |
| max-width: 300px; | |
| backdrop-filter: blur(5px); | |
| border: 1px solid rgba(100, 150, 255, 0.3); | |
| } | |
| .controls { | |
| position: absolute; | |
| top: 20px; | |
| right: 20px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| } | |
| .control-btn { | |
| background: rgba(0, 0, 30, 0.7); | |
| color: white; | |
| border: 1px solid rgba(100, 150, 255, 0.3); | |
| border-radius: 5px; | |
| padding: 8px 15px; | |
| cursor: pointer; | |
| backdrop-filter: blur(5px); | |
| transition: all 0.3s ease; | |
| } | |
| .control-btn:hover { | |
| background: rgba(20, 50, 100, 0.8); | |
| transform: translateY(-2px); | |
| } | |
| .city-label { | |
| position: absolute; | |
| background: rgba(0, 0, 0, 0.7); | |
| color: white; | |
| padding: 5px 10px; | |
| border-radius: 4px; | |
| font-size: 12px; | |
| pointer-events: none; | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| text-shadow: 0 0 3px black; | |
| border: 1px solid rgba(255, 255, 255, 0.2); | |
| } | |
| .loading-screen { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 10, 30, 0.95); | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 100; | |
| transition: opacity 1s ease-out; | |
| } | |
| .loading-spinner { | |
| width: 50px; | |
| height: 50px; | |
| border: 5px solid rgba(100, 150, 255, 0.3); | |
| border-top: 5px solid #4d9df0; | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| margin-bottom: 20px; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .pulse { | |
| display: block; | |
| width: 10px; | |
| height: 10px; | |
| border-radius: 50%; | |
| background: #ff3366; | |
| box-shadow: 0 0 0 0 rgba(255, 51, 102, 0.7); | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { | |
| transform: scale(0.8); | |
| box-shadow: 0 0 0 0 rgba(255, 51, 102, 0.7); | |
| } | |
| 70% { | |
| transform: scale(1); | |
| box-shadow: 0 0 0 10px rgba(255, 51, 102, 0); | |
| } | |
| 100% { | |
| transform: scale(0.8); | |
| box-shadow: 0 0 0 0 rgba(255, 51, 102, 0); | |
| } | |
| } | |
| .title { | |
| position: absolute; | |
| top: 20px; | |
| left: 20px; | |
| color: white; | |
| font-size: 24px; | |
| font-weight: 300; | |
| text-shadow: 0 0 10px rgba(0, 100, 255, 0.7); | |
| } | |
| .title span { | |
| color: #4d9df0; | |
| font-weight: 600; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="loading-screen" id="loading-screen"> | |
| <div class="loading-spinner"></div> | |
| <div class="text-white text-xl">Loading Earth Visualization...</div> | |
| <div class="text-blue-300 mt-4">Loading high-resolution textures</div> | |
| </div> | |
| <div class="title">Photorealistic <span>Earth</span> Globe</div> | |
| <div id="globe-container"></div> | |
| <div class="info-panel"> | |
| <h3 class="text-lg font-semibold mb-2">Earth Visualization</h3> | |
| <p class="text-sm opacity-80">High-resolution photorealistic globe with:</p> | |
| <ul class="text-xs mt-2 space-y-1"> | |
| <li>• Satellite imagery texture</li> | |
| <li>• Dynamic cloud layer</li> | |
| <li>• Atmospheric glow effect</li> | |
| <li>• Night-side city lights</li> | |
| <li>• Interactive controls</li> | |
| </ul> | |
| </div> | |
| <div class="controls"> | |
| <button class="control-btn" id="reset-view">Reset View</button> | |
| <button class="control-btn" id="toggle-rotation">Pause Rotation</button> | |
| <button class="control-btn" id="toggle-clouds">Clouds: On</button> | |
| <button class="control-btn" id="toggle-atmosphere">Atmosphere: On</button> | |
| </div> | |
| <script> | |
| // Main Three.js script | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // Scene setup | |
| const scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0x000011); | |
| scene.fog = new THREE.Fog(0x000022, 10, 20); | |
| // Camera setup | |
| const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); | |
| camera.position.z = 15; | |
| // Renderer setup | |
| const renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.setPixelRatio(window.devicePixelRatio); | |
| document.getElementById('globe-container').appendChild(renderer.domElement); | |
| // Add stars background | |
| const starGeometry = new THREE.BufferGeometry(); | |
| const starVertices = []; | |
| for (let i = 0; i < 10000; i++) { | |
| const x = (Math.random() - 0.5) * 2000; | |
| const y = (Math.random() - 0.5) * 2000; | |
| const z = (Math.random() - 0.5) * 2000; | |
| starVertices.push(x, y, z); | |
| } | |
| starGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starVertices, 3)); | |
| const starMaterial = new THREE.PointsMaterial({ color: 0xffffff, size: 0.7 }); | |
| const stars = new THREE.Points(starGeometry, starMaterial); | |
| scene.add(stars); | |
| // Create Earth group | |
| const earthGroup = new THREE.Group(); | |
| scene.add(earthGroup); | |
| // Earth parameters | |
| const earthRadius = 5; | |
| const earthSegments = 128; | |
| // Create Earth geometry | |
| const earthGeometry = new THREE.SphereGeometry(earthRadius, earthSegments, earthSegments / 2); | |
| // Earth material with textures | |
| const earthMaterial = new THREE.MeshPhongMaterial({ | |
| specular: 0x333333, | |
| shininess: 5, | |
| map: new THREE.TextureLoader().load('https://raw.githubusercontent.com/mrdoob/three.js/master/examples/textures/planets/earth_atmos_2048.jpg', () => { | |
| document.getElementById('loading-screen').style.opacity = '0'; | |
| setTimeout(() => { | |
| document.getElementById('loading-screen').style.display = 'none'; | |
| }, 1000); | |
| }), | |
| specularMap: new THREE.TextureLoader().load('https://raw.githubusercontent.com/mrdoob/three.js/master/examples/textures/planets/earth_specular_2048.jpg'), | |
| normalMap: new THREE.TextureLoader().load('https://raw.githubusercontent.com/mrdoob/three.js/master/examples/textures/planets/earth_normal_2048.jpg'), | |
| normalScale: new THREE.Vector2(0.85, 0.85) | |
| }); | |
| // Create Earth mesh | |
| const earth = new THREE.Mesh(earthGeometry, earthMaterial); | |
| earthGroup.add(earth); | |
| // Create clouds | |
| const cloudGeometry = new THREE.SphereGeometry(earthRadius * 1.005, earthSegments, earthSegments / 2); | |
| const cloudMaterial = new THREE.MeshPhongMaterial({ | |
| map: new THREE.TextureLoader().load('https://raw.githubusercontent.com/mrdoob/three.js/master/examples/textures/planets/earth_clouds_2048.png'), | |
| transparent: true, | |
| opacity: 0.8 | |
| }); | |
| const clouds = new THREE.Mesh(cloudGeometry, cloudMaterial); | |
| earthGroup.add(clouds); | |
| // Create atmosphere | |
| const atmosphereGeometry = new THREE.SphereGeometry(earthRadius * 1.02, earthSegments, earthSegments / 2); | |
| const atmosphereMaterial = new THREE.MeshPhongMaterial({ | |
| color: 0x3399ff, | |
| transparent: true, | |
| opacity: 0.15, | |
| side: THREE.BackSide | |
| }); | |
| const atmosphere = new THREE.Mesh(atmosphereGeometry, atmosphereMaterial); | |
| earthGroup.add(atmosphere); | |
| // Create night lights | |
| const lightsGeometry = new THREE.SphereGeometry(earthRadius * 0.999, earthSegments, earthSegments / 2); | |
| const lightsMaterial = new THREE.MeshBasicMaterial({ | |
| map: new THREE.TextureLoader().load('https://raw.githubusercontent.com/mrdoob/three.js/master/examples/textures/planets/earth_lights_2048.jpg'), | |
| blending: THREE.AdditiveBlending, | |
| transparent: true | |
| }); | |
| const nightLights = new THREE.Mesh(lightsGeometry, lightsMaterial); | |
| earthGroup.add(nightLights); | |
| // Add lighting | |
| const ambientLight = new THREE.AmbientLight(0x333333); | |
| scene.add(ambientLight); | |
| const sunLight = new THREE.DirectionalLight(0xffffff, 1.2); | |
| sunLight.position.set(10, 5, 7); | |
| scene.add(sunLight); | |
| // Add hemisphere light for more natural illumination | |
| const hemiLight = new THREE.HemisphereLight(0x5577dd, 0x224422, 0.1); | |
| scene.add(hemiLight); | |
| // Add orbit controls | |
| const controls = new THREE.OrbitControls(camera, renderer.domElement); | |
| controls.enableDamping = true; | |
| controls.dampingFactor = 0.05; | |
| controls.minDistance = 6; | |
| controls.maxDistance = 30; | |
| // City data | |
| const cities = [ | |
| { name: "New York", lat: 40.7128, lon: -74.0060 }, | |
| { name: "London", lat: 51.5074, lon: -0.1278 }, | |
| { name: "Tokyo", lat: 35.6895, lon: 139.6917 }, | |
| { name: "Sydney", lat: -33.8688, lon: 151.2093 }, | |
| { name: "Rio de Janeiro", lat: -22.9068, lon: -43.1729 }, | |
| { name: "Cairo", lat: 30.0444, lon: 31.2357 }, | |
| { name: "Moscow", lat: 55.7558, lon: 37.6173 }, | |
| { name: "Beijing", lat: 39.9042, lon: 116.4074 } | |
| ]; | |
| // Create city markers | |
| const cityMarkers = []; | |
| const markerGroup = new THREE.Group(); | |
| earthGroup.add(markerGroup); | |
| cities.forEach(city => { | |
| const marker = new THREE.Mesh( | |
| new THREE.SphereGeometry(0.05, 16, 16), | |
| new THREE.MeshBasicMaterial({ color: 0xff3366 }) | |
| ); | |
| // Convert lat/lon to 3D position | |
| const phi = (90 - city.lat) * Math.PI / 180; | |
| const theta = (city.lon + 180) * Math.PI / 180; | |
| marker.position.x = -(earthRadius * 1.01) * Math.sin(phi) * Math.cos(theta); | |
| marker.position.y = (earthRadius * 1.01) * Math.cos(phi); | |
| marker.position.z = (earthRadius * 1.01) * Math.sin(phi) * Math.sin(theta); | |
| marker.userData = { name: city.name }; | |
| markerGroup.add(marker); | |
| cityMarkers.push(marker); | |
| // Create label element | |
| const label = document.createElement('div'); | |
| label.className = 'city-label'; | |
| label.textContent = city.name; | |
| document.body.appendChild(label); | |
| marker.userData.label = label; | |
| }); | |
| // Raycaster for interaction | |
| const raycaster = new THREE.Raycaster(); | |
| const mouse = new THREE.Vector2(); | |
| // Handle mouse events | |
| let highlightedMarker = null; | |
| function onMouseMove(event) { | |
| // Calculate mouse position in normalized device coordinates | |
| mouse.x = (event.clientX / window.innerWidth) * 2 - 1; | |
| mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; | |
| // Update the picking ray with the camera and mouse position | |
| raycaster.setFromCamera(mouse, camera); | |
| // Calculate objects intersecting the picking ray | |
| const intersects = raycaster.intersectObjects(cityMarkers); | |
| // Reset all labels | |
| cityMarkers.forEach(marker => { | |
| marker.material.color.set(0xff3366); | |
| marker.userData.label.style.opacity = '0'; | |
| }); | |
| if (intersects.length > 0) { | |
| const marker = intersects[0].object; | |
| marker.material.color.set(0xffff00); | |
| marker.userData.label.style.opacity = '1'; | |
| // Position label | |
| const vector = new THREE.Vector3(); | |
| vector.setFromMatrixPosition(marker.matrixWorld); | |
| vector.project(camera); | |
| const x = (vector.x * 0.5 + 0.5) * window.innerWidth; | |
| const y = (-vector.y * 0.5 + 0.5) * window.innerHeight; | |
| marker.userData.label.style.left = `${x}px`; | |
| marker.userData.label.style.top = `${y}px`; | |
| } | |
| } | |
| function onMouseClick(event) { | |
| raycaster.setFromCamera(mouse, camera); | |
| const intersects = raycaster.intersectObjects(cityMarkers); | |
| if (intersects.length > 0) { | |
| const marker = intersects[0].object; | |
| // Reset previously highlighted marker | |
| if (highlightedMarker) { | |
| highlightedMarker.scale.set(1, 1, 1); | |
| } | |
| // Highlight clicked marker | |
| marker.scale.set(1.5, 1.5, 1.5); | |
| highlightedMarker = marker; | |
| // Create pulse effect | |
| const pulse = document.createElement('div'); | |
| pulse.className = 'pulse'; | |
| document.body.appendChild(pulse); | |
| // Position pulse | |
| const vector = new THREE.Vector3(); | |
| vector.setFromMatrixPosition(marker.matrixWorld); | |
| vector.project(camera); | |
| const x = (vector.x * 0.5 + 0.5) * window.innerWidth; | |
| const y = (-vector.y * 0.5 + 0.5) * window.innerHeight; | |
| pulse.style.position = 'absolute'; | |
| pulse.style.left = `${x - 5}px`; | |
| pulse.style.top = `${y - 5}px`; | |
| // Remove pulse after animation | |
| setTimeout(() => { | |
| document.body.removeChild(pulse); | |
| }, 2000); | |
| } | |
| } | |
| // Add event listeners | |
| window.addEventListener('mousemove', onMouseMove, false); | |
| window.addEventListener('click', onMouseClick, false); | |
| // Handle window resize | |
| function onWindowResize() { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| } | |
| window.addEventListener('resize', onWindowResize, false); | |
| // Animation variables | |
| let autoRotate = true; | |
| let cloudsVisible = true; | |
| let atmosphereVisible = true; | |
| // Control buttons | |
| document.getElementById('reset-view').addEventListener('click', () => { | |
| controls.reset(); | |
| }); | |
| document.getElementById('toggle-rotation').addEventListener('click', () => { | |
| autoRotate = !autoRotate; | |
| document.getElementById('toggle-rotation').textContent = | |
| autoRotate ? 'Pause Rotation' : 'Resume Rotation'; | |
| }); | |
| document.getElementById('toggle-clouds').addEventListener('click', () => { | |
| cloudsVisible = !cloudsVisible; | |
| clouds.visible = cloudsVisible; | |
| document.getElementById('toggle-clouds').textContent = | |
| `Clouds: ${cloudsVisible ? 'On' : 'Off'}`; | |
| }); | |
| document.getElementById('toggle-atmosphere').addEventListener('click', () => { | |
| atmosphereVisible = !atmosphereVisible; | |
| atmosphere.visible = atmosphereVisible; | |
| document.getElementById('toggle-atmosphere').textContent = | |
| `Atmosphere: ${atmosphereVisible ? 'On' : 'Off'}`; | |
| }); | |
| // Animation loop | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| // Auto-rotate Earth | |
| if (autoRotate) { | |
| earthGroup.rotation.y += 0.002; | |
| } | |
| // Rotate clouds slightly faster than Earth | |
| clouds.rotation.y += 0.0005; | |
| // Update controls | |
| controls.update(); | |
| // Render scene | |
| renderer.render(scene, camera); | |
| } | |
| // Start animation | |
| animate(); | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=MagicBullets/rotating-earth" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |