rotating-earth / index.html
MagicBullets's picture
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
<!DOCTYPE html>
<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>