----优化登录界面
This commit is contained in:
335
src/views/secondDev/Earth3D.vue
Normal file
335
src/views/secondDev/Earth3D.vue
Normal file
@ -0,0 +1,335 @@
|
||||
<template>
|
||||
<div ref="earthContainer" class="earth-container"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
|
||||
import * as THREE from 'three';
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
|
||||
// 地球纹理URL
|
||||
import earthBlueMarble from '../../assets/images/earth-blue-marble.jpg';
|
||||
|
||||
const earthContainer = ref<HTMLDivElement | null>(null);
|
||||
|
||||
let scene: THREE.Scene | null = null;
|
||||
let camera: THREE.PerspectiveCamera | null = null;
|
||||
let renderer: THREE.WebGLRenderer | null = null;
|
||||
let controls: OrbitControls | null = null;
|
||||
let earth: THREE.Mesh | null = null;
|
||||
let markers: THREE.Mesh[] = [];
|
||||
let animationId: number | null = null;
|
||||
|
||||
// 城市数据 - 红色主要城市
|
||||
const primaryCities = [
|
||||
{ name: '广州', lat: 23.1291, lng: 113.2644, color: 0xFA2D30 },
|
||||
];
|
||||
|
||||
// 城市数据 - 黄色次要城市
|
||||
const secondaryCities = [
|
||||
{ name: '北京', lat: 39.9042, lng: 116.4074, color: 0xFFF963 },
|
||||
{ name: '上海', lat: 31.2304, lng: 121.4737, color: 0xFFF963 },
|
||||
{ name: '香港', lat: 22.3964, lng: 114.1095, color: 0xFFF963 },
|
||||
{ name: '新加坡', lat: 1.3521, lng: 103.8198, color: 0xFFF963 },
|
||||
{ name: '东京', lat: 35.6824, lng: 139.759, color: 0xFFF963 },
|
||||
{ name: '伦敦', lat: 51.5074, lng: -0.1278, color: 0xFFF963 },
|
||||
{ name: '迪拜', lat: 25.276987, lng: 55.296249, color: 0xFFF963 },
|
||||
{ name: '纽约', lat: 40.748817, lng: -73.985428, color: 0xFFF963 },
|
||||
{ name: '旧金山', lat: 37.7749, lng: -122.4194, color: 0xFFF963 },
|
||||
{ name: '惉尼', lat: -33.8688, lng: 151.2093, color: 0xFFF963 },
|
||||
{ name: '巴黎', lat: 48.8566, lng: 2.3522, color: 0xFFF963 },
|
||||
];
|
||||
|
||||
const EARTH_RADIUS = 1.0;
|
||||
|
||||
const initScene = () => {
|
||||
if (!earthContainer.value) return;
|
||||
|
||||
const width = earthContainer.value.clientWidth;
|
||||
const height = earthContainer.value.clientHeight;
|
||||
|
||||
// 创建场景
|
||||
scene = new THREE.Scene();
|
||||
|
||||
// 创建相机
|
||||
camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 100);
|
||||
camera.position.set(0.0, 1.5, 3.0);
|
||||
|
||||
// 创建渲染器
|
||||
renderer = new THREE.WebGLRenderer({
|
||||
antialias: true,
|
||||
alpha: true,
|
||||
});
|
||||
renderer.setSize(width, height);
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
earthContainer.value.appendChild(renderer.domElement);
|
||||
|
||||
// 创建 OrbitControls
|
||||
controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.autoRotate = true;
|
||||
controls.autoRotateSpeed = -1.0;
|
||||
controls.enablePan = false;
|
||||
controls.enableZoom = false;
|
||||
controls.enableDamping = true;
|
||||
controls.dampingFactor = 0.05;
|
||||
|
||||
// 添加光源
|
||||
addLights();
|
||||
|
||||
// 创建地球
|
||||
createEarth();
|
||||
|
||||
// 创建大气层
|
||||
createAtmosphere();
|
||||
};
|
||||
|
||||
const addLights = () => {
|
||||
if (!scene) return;
|
||||
|
||||
// 环境光
|
||||
const ambient = new THREE.AmbientLight(0xffffff, 1);
|
||||
scene.add(ambient);
|
||||
|
||||
// 方向光
|
||||
const directional = new THREE.DirectionalLight(0xffffff, 0.5);
|
||||
directional.position.set(5.0, 2.0, 5.0).normalize();
|
||||
scene.add(directional);
|
||||
};
|
||||
|
||||
const createEarth = () => {
|
||||
if (!scene) return;
|
||||
|
||||
const loader = new THREE.TextureLoader();
|
||||
|
||||
loader.load(earthBlueMarble, (texture) => {
|
||||
if (!scene) return;
|
||||
|
||||
// 设置各向异性过滤
|
||||
if (renderer) {
|
||||
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
|
||||
}
|
||||
|
||||
const geometry = new THREE.SphereGeometry(EARTH_RADIUS, 64, 48);
|
||||
const material = new THREE.MeshStandardMaterial({
|
||||
color: 0xFFFFFF,
|
||||
map: texture,
|
||||
roughness: 0.6,
|
||||
metalness: 0.1,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
|
||||
earth = new THREE.Mesh(geometry, material);
|
||||
scene.add(earth);
|
||||
|
||||
// 地球加载完成后添加标记点
|
||||
createMarkers();
|
||||
});
|
||||
};
|
||||
|
||||
const createAtmosphere = () => {
|
||||
if (!scene) return;
|
||||
|
||||
const atmosphereGeometry = new THREE.SphereGeometry(1.05, 64, 48);
|
||||
const atmosphereMaterial = new THREE.ShaderMaterial({
|
||||
vertexShader: `
|
||||
varying vec3 vertexNormal;
|
||||
void main() {
|
||||
vertexNormal = normalize(normalMatrix * normal);
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
varying vec3 vertexNormal;
|
||||
void main() {
|
||||
float intensity = pow(0.6 - dot(vertexNormal, vec3(0, 0, 1.0)), 2.0);
|
||||
gl_FragColor = vec4(0.3, 0.6, 1.0, 0.8) * intensity;
|
||||
}
|
||||
`,
|
||||
blending: THREE.AdditiveBlending,
|
||||
side: THREE.BackSide,
|
||||
transparent: true,
|
||||
});
|
||||
|
||||
const atmosphere = new THREE.Mesh(atmosphereGeometry, atmosphereMaterial);
|
||||
atmosphere.scale.set(1.1, 1.1, 1.1);
|
||||
atmosphere.position.x = -0.02;
|
||||
atmosphere.position.y = 0.02;
|
||||
scene.add(atmosphere);
|
||||
};
|
||||
|
||||
// 经纬度转换为3D坐标
|
||||
const latLngToVector3 = (lat: number, lng: number, radius: number): THREE.Vector3 => {
|
||||
const phi = (90 - lat) * (Math.PI / 180);
|
||||
const theta = (lng + 180) * (Math.PI / 180);
|
||||
|
||||
const x = -radius * Math.sin(phi) * Math.cos(theta);
|
||||
const y = radius * Math.cos(phi);
|
||||
const z = radius * Math.sin(phi) * Math.sin(theta);
|
||||
|
||||
return new THREE.Vector3(x, y, z);
|
||||
};
|
||||
|
||||
// 创建标记点
|
||||
const createMarkers = () => {
|
||||
if (!scene) return;
|
||||
|
||||
const markerRadius = 1.06;
|
||||
|
||||
// 创建主要城市标记(红色,较大)
|
||||
primaryCities.forEach((city) => {
|
||||
const marker = createCityMarker(city.lat, city.lng, markerRadius, city.color, 0.02, true);
|
||||
if (marker) {
|
||||
scene!.add(marker);
|
||||
markers.push(marker);
|
||||
}
|
||||
});
|
||||
|
||||
// 创建次要城市标记(黄色,较小)
|
||||
secondaryCities.forEach((city) => {
|
||||
const marker = createCityMarker(city.lat, city.lng, markerRadius, city.color, 0.01, false);
|
||||
if (marker) {
|
||||
scene!.add(marker);
|
||||
markers.push(marker);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 创建城市标记
|
||||
const createCityMarker = (
|
||||
lat: number,
|
||||
lng: number,
|
||||
radius: number,
|
||||
color: number,
|
||||
size: number,
|
||||
hasRing: boolean
|
||||
): THREE.Mesh | null => {
|
||||
// 创建圆形标记
|
||||
const geometry = new THREE.CircleGeometry(size, 32);
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
color: color,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
const marker = new THREE.Mesh(geometry, material);
|
||||
|
||||
// 添加外环
|
||||
if (hasRing) {
|
||||
const ringGeometry = new THREE.RingGeometry(size * 1.2, size * 1.7, 32);
|
||||
const ringMaterial = new THREE.MeshBasicMaterial({
|
||||
color: color,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
const ring = new THREE.Mesh(ringGeometry, ringMaterial);
|
||||
marker.add(ring);
|
||||
} else {
|
||||
// 次要城市添加较小的外环
|
||||
const ringGeometry = new THREE.RingGeometry(size * 1.5, size * 1.8, 32);
|
||||
const ringMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0x898A51,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
const ring = new THREE.Mesh(ringGeometry, ringMaterial);
|
||||
marker.add(ring);
|
||||
}
|
||||
|
||||
// 计算位置
|
||||
const position = latLngToVector3(lat, lng, radius);
|
||||
marker.position.copy(position);
|
||||
|
||||
// 让标记朝向球心外侧
|
||||
marker.lookAt(new THREE.Vector3(0, 0, 0));
|
||||
marker.rotateX(Math.PI);
|
||||
|
||||
return marker;
|
||||
};
|
||||
|
||||
const animate = () => {
|
||||
if (!scene || !camera || !renderer) return;
|
||||
|
||||
animationId = requestAnimationFrame(animate);
|
||||
|
||||
// 更新控制器
|
||||
if (controls) {
|
||||
controls.update();
|
||||
}
|
||||
|
||||
// 让标记点始终面向相机
|
||||
markers.forEach((marker) => {
|
||||
marker.quaternion.copy(camera!.quaternion);
|
||||
});
|
||||
|
||||
renderer.render(scene, camera);
|
||||
};
|
||||
|
||||
const onResize = () => {
|
||||
if (!earthContainer.value || !camera || !renderer) return;
|
||||
|
||||
const width = earthContainer.value.clientWidth;
|
||||
const height = earthContainer.value.clientHeight;
|
||||
|
||||
camera.aspect = width / height;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(width, height);
|
||||
};
|
||||
|
||||
const dispose = () => {
|
||||
if (animationId) {
|
||||
cancelAnimationFrame(animationId);
|
||||
}
|
||||
|
||||
if (controls) {
|
||||
controls.dispose();
|
||||
}
|
||||
|
||||
// 清理标记
|
||||
markers.forEach((marker) => {
|
||||
marker.geometry?.dispose();
|
||||
if (marker.material instanceof THREE.Material) {
|
||||
marker.material.dispose();
|
||||
}
|
||||
});
|
||||
markers = [];
|
||||
|
||||
// 清理地球
|
||||
if (earth) {
|
||||
earth.geometry?.dispose();
|
||||
if (earth.material instanceof THREE.Material) {
|
||||
earth.material.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
if (renderer) {
|
||||
renderer.dispose();
|
||||
if (earthContainer.value && renderer.domElement) {
|
||||
earthContainer.value.removeChild(renderer.domElement);
|
||||
}
|
||||
}
|
||||
|
||||
scene = null;
|
||||
camera = null;
|
||||
renderer = null;
|
||||
controls = null;
|
||||
earth = null;
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick();
|
||||
initScene();
|
||||
animate();
|
||||
window.addEventListener('resize', onResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
dispose();
|
||||
window.removeEventListener('resize', onResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.earth-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 50;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user