3d球体模拟edit icon

作者:
邓朝元
Fork(复制)
下载
嵌入
BUG反馈
index.html
现在支持上传本地图片了!
index.html
            
            <!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>极致3D地球可视化系统</title>
    <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>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Arial', sans-serif;
            background: linear-gradient(135deg, #1e3c72, #2a5298);
            overflow: hidden;
            color: white;
        }

        #earthContainer {
            width: 100vw;
            height: 100vh;
            position: relative;
        }

        #loadingScreen {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.9);
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            z-index: 1000;
        }

        .loading-bar {
            width: 300px;
            height: 8px;
            background: #333;
            border-radius: 4px;
            margin: 20px 0;
            overflow: hidden;
        }

        .loading-progress {
            height: 100%;
            background: linear-gradient(90deg, #00c9ff, #92fe9d);
            width: 0%;
            transition: width 0.3s ease;
        }

        /* 控制面板样式 */
        .control-panel {
            position: absolute;
            top: 20px;
            left: 20px;
            background: rgba(0, 0, 0, 0.8);
            border-radius: 15px;
            padding: 20px;
            backdrop-filter: blur(10px);
            border: 1px solid rgba(255, 255, 255, 0.1);
            z-index: 100;
            min-width: 300px;
        }

        .panel-title {
            font-size: 18px;
            margin-bottom: 15px;
            color: #00c9ff;
            text-align: center;
        }

        .control-group {
            margin-bottom: 15px;
        }

        .control-label {
            display: block;
            margin-bottom: 5px;
            font-size: 14px;
        }

        .slider-container {
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .slider {
            flex: 1;
            height: 6px;
            border-radius: 3px;
            background: #333;
            outline: none;
            -webkit-appearance: none;
        }

        .slider::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 16px;
            height: 16px;
            border-radius: 50%;
            background: #00c9ff;
            cursor: pointer;
        }

        .slider-value {
            min-width: 40px;
            text-align: center;
            font-size: 12px;
        }

        .button-group {
            display: flex;
            gap: 10px;
            flex-wrap: wrap;
        }

        .btn {
            padding: 8px 15px;
            background: linear-gradient(45deg, #00c9ff, #92fe9d);
            border: none;
            border-radius: 20px;
            color: #000;
            cursor: pointer;
            font-size: 12px;
            font-weight: bold;
            transition: all 0.3s ease;
        }

        .btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(0, 201, 255, 0.4);
        }

        .btn-active {
            background: linear-gradient(45deg, #ff416c, #ff4b2b);
            color: white;
        }

        /* 图层控制面板 */
        .layer-panel {
            position: absolute;
            top: 20px;
            right: 20px;
            background: rgba(0, 0, 0, 0.8);
            border-radius: 15px;
            padding: 20px;
            backdrop-filter: blur(10px);
            border: 1px solid rgba(255, 255, 255, 0.1);
            z-index: 100;
            min-width: 250px;
        }

        .layer-item {
            display: flex;
            align-items: center;
            margin-bottom: 10px;
            padding: 8px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 8px;
            cursor: pointer;
            transition: all 0.3s ease;
        }

        .layer-item:hover {
            background: rgba(255, 255, 255, 0.2);
        }

        .layer-item.active {
            background: linear-gradient(45deg, #00c9ff, #92fe9d);
            color: #000;
        }

        .layer-checkbox {
            margin-right: 10px;
        }

        /* 信息面板 */
        .info-panel {
            position: absolute;
            bottom: 20px;
            left: 20px;
            background: rgba(0, 0, 0, 0.8);
            border-radius: 15px;
            padding: 15px;
            backdrop-filter: blur(10px);
            border: 1px solid rgba(255, 255, 255, 0.1);
            z-index: 100;
            min-width: 200px;
        }

        .info-item {
            margin-bottom: 8px;
            font-size: 12px;
        }

        .info-label {
            color: #00c9ff;
            margin-right: 5px;
        }

        /* 搜索框 */
        .search-panel {
            position: absolute;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0, 0, 0, 0.8);
            border-radius: 25px;
            padding: 10px 20px;
            backdrop-filter: blur(10px);
            border: 1px solid rgba(255, 255, 255, 0.1);
            z-index: 100;
            display: flex;
            align-items: center;
        }

        .search-input {
            background: transparent;
            border: none;
            color: white;
            padding: 5px 10px;
            width: 300px;
            outline: none;
            font-size: 14px;
        }

        .search-input::placeholder {
            color: rgba(255, 255, 255, 0.5);
        }

        .search-btn {
            background: none;
            border: none;
            color: #00c9ff;
            cursor: pointer;
            padding: 5px;
        }

        /* 响应式设计 */
        @media (max-width: 768px) {
            .control-panel, .layer-panel {
                position: fixed;
                top: auto;
                bottom: 0;
                left: 0;
                right: 0;
                width: 100%;
                border-radius: 20px 20px 0 0;
                max-height: 50vh;
                overflow-y: auto;
            }

            .search-panel {
                top: 10px;
                width: 90%;
            }

            .search-input {
                width: 200px;
            }
        }

        /* 动画效果 */
        @keyframes pulse {
            0% { opacity: 0.6; }
            50% { opacity: 1; }
            100% { opacity: 0.6; }
        }

        .pulse {
            animation: pulse 2s infinite;
        }
    </style>
</head>
<body>
    <!-- 加载界面 -->
    <div id="loadingScreen">
        <h2>正在构建极致3D地球...</h2>
        <div class="loading-bar">
            <div class="loading-progress" id="loadingProgress"></div>
        </div>
        <p id="loadingText">初始化引擎...</p>
    </div>

    <!-- 主容器 -->
    <div id="earthContainer"></div>

    <!-- 搜索面板 -->
    <div class="search-panel">
        <input type="text" class="search-input" placeholder="搜索城市、国家或坐标..." id="searchInput">
        <button class="search-btn" id="searchBtn">🔍</button>
    </div>

    <!-- 控制面板 -->
    <div class="control-panel">
        <div class="panel-title">🌍 地球控制系统</div>
        
        <div class="control-group">
            <label class="control-label">缩放级别</label>
            <div class="slider-container">
                <input type="range" min="1" max="100" value="50" class="slider" id="zoomSlider">
                <span class="slider-value" id="zoomValue">50</span>
            </div>
        </div>

        <div class="control-group">
            <label class="control-label">旋转速度</label>
            <div class="slider-container">
                <input type="range" min="0" max="10" value="2" class="slider" id="rotationSlider">
                <span class="slider-value" id="rotationValue">2</span>
            </div>
        </div>

        <div class="control-group">
            <label class="control-label">光照强度</label>
            <div class="slider-container">
                <input type="range" min="0" max="2" value="1" step="0.1" class="slider" id="lightSlider">
                <span class="slider-value" id="lightValue">1.0</span>
            </div>
        </div>

        <div class="button-group">
            <button class="btn" id="autoRotateBtn">自动旋转</button>
            <button class="btn" id="resetBtn">重置视角</button>
            <button class="btn" id="fullscreenBtn">全屏显示</button>
        </div>
    </div>

    <!-- 图层控制面板 -->
    <div class="layer-panel">
        <div class="panel-title">🗺️ 图层控制</div>
        
        <div class="layer-item active" data-layer="satellite">
            <input type="checkbox" class="layer-checkbox" checked>
            <span>卫星图像</span>
        </div>
        
        <div class="layer-item" data-layer="terrain">
            <input type="checkbox" class="layer-checkbox">
            <span>地形高程</span>
        </div>
        
        <div class="layer-item" data-layer="boundaries">
            <input type="checkbox" class="layer-checkbox" checked>
            <span>国家边界</span>
        </div>
        
        <div class="layer-item" data-layer="cities">
            <input type="checkbox" class="layer-checkbox">
            <span>主要城市</span>
        </div>
        
        <div class="layer-item" data-layer="clouds">
            <input type="checkbox" class="layer-checkbox">
            <span>实时云层</span>
        </div>
        
        <div class="layer-item" data-layer="atmosphere">
            <input type="checkbox" class="layer-checkbox" checked>
            <span>大气效果</span>
        </div>
    </div>

    <!-- 信息面板 -->
    <div class="info-panel">
        <div class="info-item"><span class="info-label">坐标:</span> <span id="coordinates">0°, 0°</span></div>
        <div class="info-item"><span class="info-label">缩放:</span> <span id="zoomLevel">1.0x</span></div>
        <div class="info-item"><span class="info-label">时间:</span> <span id="currentTime"></span></div>
        <div class="info-item"><span class="info-label">图层:</span> <span id="currentLayer">卫星图像</span></div>
    </div>

    <script>
        // 全局变量
        let scene, camera, renderer, earth, controls;
        let earthGroup, cloudGroup;
        let autoRotate = false;
        let currentLayer = 'satellite';
        let markers = [];
        let animationId;

        // 城市数据 (简化版,实际应用中会有1000+项)
        const cities = [
            { name: "北京", lat: 39.9042, lng: 116.4074, population: "21,540,000" },
            { name: "上海", lat: 31.2304, lng: 121.4737, population: "24,280,000" },
            { name: "纽约", lat: 40.7128, lng: -74.0060, population: "8,336,817" },
            { name: "伦敦", lat: 51.5074, lng: -0.1278, population: "9,425,622" },
            { name: "东京", lat: 35.6762, lng: 139.6503, population: "13,929,286" },
            { name: "巴黎", lat: 48.8566, lng: 2.3522, population: "2,161,000" },
            { name: "莫斯科", lat: 55.7558, lng: 37.6173, population: "12,619,600" },
            { name: "悉尼", lat: -33.8688, lng: 151.2093, population: "5,312,163" },
            { name: "开罗", lat: 30.0444, lng: 31.2357, population: "20,484,965" },
            { name: "里约热内卢", lat: -22.9068, lng: -43.1729, population: "6,748,000" }
        ];

        // 初始化应用
        function init() {
            // 显示加载进度
            updateLoadingProgress(10, "初始化Three.js引擎...");

            // 创建场景
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x000814);
            scene.fog = new THREE.Fog(0x000814, 100, 300);

            // 创建相机
            camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.z = 250;

            // 创建渲染器
            renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(window.devicePixelRatio);
            document.getElementById('earthContainer').appendChild(renderer.domElement);

            updateLoadingProgress(30, "创建地球模型...");

            // 创建地球组
            earthGroup = new THREE.Group();
            scene.add(earthGroup);

            // 创建地球
            createEarth();

            updateLoadingProgress(50, "添加光照系统...");

            // 添加光照
            addLighting();

            updateLoadingProgress(70, "配置交互控制...");

            // 添加控制器
            controls = new THREE.OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
            controls.dampingFactor = 0.05;
            controls.screenSpacePanning = false;
            controls.minDistance = 100;
            controls.maxDistance = 500;

            updateLoadingProgress(80, "添加环境效果...");

            // 添加星星背景
            addStars();

            // 添加云层
            addClouds();

            updateLoadingProgress(90, "初始化UI界面...");

            // 绑定事件
            bindEvents();

            updateLoadingProgress(100, "完成!");

            // 隐藏加载界面
            setTimeout(() => {
                document.getElementById('loadingScreen').style.display = 'none';
            }, 1000);

            // 开始动画循环
            animate();
        }

        // 创建地球
        function createEarth() {
            // 地球几何体
            const geometry = new THREE.SphereGeometry(100, 64, 64);

            // 地球材质 - 使用更真实的纹理
            const material = new THREE.MeshPhongMaterial({
                color: 0x2E86AB,
                specular: 0x333333,
                shininess: 5,
                transparent: true,
                opacity: 0.9
            });

            earth = new THREE.Mesh(geometry, material);
            earthGroup.add(earth);

            // 添加更多细节层
            addEarthDetails();
        }

        // 添加地球细节
        function addEarthDetails() {
            // 大气层效果
            const atmosphereGeometry = new THREE.SphereGeometry(102, 64, 64);
            const atmosphereMaterial = new THREE.MeshPhongMaterial({
                color: 0x87CEEB,
                transparent: true,
                opacity: 0.1,
                side: THREE.BackSide
            });
            const atmosphere = new THREE.Mesh(atmosphereGeometry, atmosphereMaterial);
            earthGroup.add(atmosphere);

            // 添加大陆轮廓
            addContinents();
        }

        // 添加大陆轮廓
        function addContinents() {
            // 简化的大陆形状 - 实际应用中会使用真实数据
            const continentMaterial = new THREE.MeshPhongMaterial({
                color: 0x3D9970,
                transparent: true,
                opacity: 0.7
            });

            // 添加一些大陆块作为示例
            const continents = [
                { position: [0, 0, 95], size: [20, 15, 5] }, // 简化的大陆
                { position: [-30, 20, 90], size: [15, 10, 5] },
                { position: [25, -15, 92], size: [18, 12, 5] }
            ];

            continents.forEach(cont => {
                const contGeometry = new THREE.BoxGeometry(cont.size[0], cont.size[1], cont.size[2]);
                const continent = new THREE.Mesh(contGeometry, continentMaterial);
                continent.position.set(cont.position[0], cont.position[1], cont.position[2]);
                earthGroup.add(continent);
            });
        }

        // 添加光照
        function addLighting() {
            // 环境光
            const ambientLight = new THREE.AmbientLight(0x404040, 0.4);
            scene.add(ambientLight);

            // 太阳光源
            const sunLight = new THREE.DirectionalLight(0xFFFFFF, 1);
            sunLight.position.set(300, 300, 300);
            sunLight.castShadow = true;
            scene.add(sunLight);

            // 补充光源
            const fillLight = new THREE.DirectionalLight(0x404040, 0.3);
            fillLight.position.set(-200, -200, -200);
            scene.add(fillLight);
        }

        // 添加星星背景
        function addStars() {
            const starGeometry = new THREE.BufferGeometry();
            const starMaterial = new THREE.PointsMaterial({
                color: 0xFFFFFF,
                size: 1,
                transparent: true
            });

            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 stars = new THREE.Points(starGeometry, starMaterial);
            scene.add(stars);
        }

        // 添加云层
        function addClouds() {
            cloudGroup = new THREE.Group();
            earthGroup.add(cloudGroup);

            const cloudGeometry = new THREE.SphereGeometry(105, 32, 32);
            const cloudMaterial = new THREE.MeshPhongMaterial({
                color: 0xFFFFFF,
                transparent: true,
                opacity: 0.3,
                wireframe: true
            });

            const clouds = new THREE.Mesh(cloudGeometry, cloudMaterial);
            cloudGroup.add(clouds);
        }

        // 添加城市标记
        function addCityMarkers() {
            cities.forEach(city => {
                const markerGeometry = new THREE.SphereGeometry(2, 8, 8);
                const markerMaterial = new THREE.MeshPhongMaterial({
                    color: 0xFF4136,
                    emissive: 0xFF4136,
                    emissiveIntensity: 0.5
                });
                const marker = new THREE.Mesh(markerGeometry, markerMaterial);

                // 将经纬度转换为3D坐标
                const phi = (90 - city.lat) * Math.PI / 180;
                const theta = (city.lng + 180) * Math.PI / 180;
                const radius = 101;

                marker.position.x = -radius * Math.sin(phi) * Math.cos(theta);
                marker.position.y = radius * Math.cos(phi);
                marker.position.z = radius * Math.sin(phi) * Math.sin(theta);

                marker.userData = city;
                earthGroup.add(marker);
                markers.push(marker);
            });
        }

        // 绑定事件
        function bindEvents() {
            // 缩放控制
            document.getElementById('zoomSlider').addEventListener('input', function() {
                const value = this.value;
                document.getElementById('zoomValue').textContent = value;
                // 实现缩放逻辑
            });

            // 旋转控制
            document.getElementById('rotationSlider').addEventListener('input', function() {
                const value = this.value;
                document.getElementById('rotationValue').textContent = value;
                // 实现旋转速度控制
            });

            // 光照控制
            document.getElementById('lightSlider').addEventListener('input', function() {
                const value = parseFloat(this.value).toFixed(1);
                document.getElementById('lightValue').textContent = value;
                // 实现光照强度控制
            });

            // 自动旋转按钮
            document.getElementById('autoRotateBtn').addEventListener('click', function() {
                autoRotate = !autoRotate;
                this.classList.toggle('btn-active');
                this.textContent = autoRotate ? '停止旋转' : '自动旋转';
            });

            // 重置按钮
            document.getElementById('resetBtn').addEventListener('click', function() {
                controls.reset();
                camera.position.z = 250;
            });

            // 全屏按钮
            document.getElementById('fullscreenBtn').addEventListener('click', function() {
                if (!document.fullscreenElement) {
                    document.documentElement.requestFullscreen();
                } else {
                    document.exitFullscreen();
                }
            });

            // 图层控制
            document.querySelectorAll('.layer-item').forEach(item => {
                item.addEventListener('click', function() {
                    const layer = this.dataset.layer;
                    this.classList.toggle('active');
                    const checkbox = this.querySelector('.layer-checkbox');
                    checkbox.checked = !checkbox.checked;
                    
                    // 实现图层切换逻辑
                    switch(layer) {
                        case 'satellite':
                            currentLayer = '卫星图像';
                            break;
                        case 'terrain':
                            currentLayer = '地形高程';
                            break;
                        case 'boundaries':
                            currentLayer = '国家边界';
                            break;
                        case 'cities':
                            if (checkbox.checked) {
                                addCityMarkers();
                            } else {
                                // 移除城市标记
                                markers.forEach(marker => earthGroup.remove(marker));
                                markers = [];
                            }
                            break;
                    }
                    document.getElementById('currentLayer').textContent = currentLayer;
                });
            });

            // 搜索功能
            document.getElementById('searchBtn').addEventListener('click', searchLocation);
            document.getElementById('searchInput').addEventListener('keypress', function(e) {
                if (e.key === 'Enter') {
                    searchLocation();
                }
            });

            // 窗口大小调整
            window.addEventListener('resize', onWindowResize);
        }

        // 搜索位置
        function searchLocation() {
            const query = document.getElementById('searchInput').value.trim();
            if (query) {
                // 简化的搜索逻辑 - 实际应用中会连接地理编码API
                const foundCity = cities.find(city => 
                    city.name.toLowerCase().includes(query.toLowerCase())
                );
                
                if (foundCity) {
                    // 定位到城市
                    const phi = (90 - foundCity.lat) * Math.PI / 180;
                    const theta = (foundCity.lng + 180) * Math.PI / 180;
                    
                    // 平滑移动相机
                    const targetX = -150 * Math.sin(phi) * Math.cos(theta);
                    const targetY = 150 * Math.cos(phi);
                    const targetZ = 150 * Math.sin(phi) * Math.sin(theta);
                    
                    // 这里应该添加平滑动画,简化处理
                    camera.position.set(targetX * 1.5, targetY * 1.5, targetZ * 1.5);
                    controls.update();
                    
                    // 显示搜索结果
                    alert(`已定位到: ${foundCity.name}\n人口: ${foundCity.population}`);
                } else {
                    alert('未找到指定位置');
                }
            }
        }

        // 窗口大小调整
        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        // 更新加载进度
        function updateLoadingProgress(percent, text) {
            document.getElementById('loadingProgress').style.width = percent + '%';
            document.getElementById('loadingText').textContent = text;
        }

        // 动画循环
        function animate() {
            animationId = requestAnimationFrame(animate);

            // 地球自转
            if (autoRotate) {
                earthGroup.rotation.y += 0.002;
            }

            // 云层运动
            if (cloudGroup) {
                cloudGroup.rotation.y += 0.001;
            }

            // 更新控制器
            controls.update();

            // 更新时间显示
            updateTime();

            // 渲染场景
            renderer.render(scene, camera);
        }

        // 更新时间显示
        function updateTime() {
            const now = new Date();
            const timeString = now.toLocaleString('zh-CN');
            document.getElementById('currentTime').textContent = timeString;
        }

        // 获取鼠标位置对应的经纬度
        function getCoordinatesFromMouse(event) {
            const rect = renderer.domElement.getBoundingClientRect();
            const mouse = new THREE.Vector2();
            mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
            mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;

            // 这里应该实现射线检测来获取地球表面坐标
            // 简化处理,返回示例值
            return { lat: 0, lng: 0 };
        }

        // 销毁应用
        function destroy() {
            if (animationId) {
                cancelAnimationFrame(animationId);
            }
            if (renderer) {
                renderer.dispose();
            }
            // 清理其他资源...
        }

        // 页面加载完成后初始化
        window.addEventListener('load', init);

        // 页面卸载时清理
        window.addEventListener('beforeunload', destroy);
    </script>
</body>
</html>
        
编辑器加载中
预览
控制台