220 lines
6.5 KiB
JavaScript
220 lines
6.5 KiB
JavaScript
// 3D 地球效果(首页 banner 右侧)基于 three-globe
|
||
// 依赖:全局 THREE 和 ThreeGlobe(在 index.html 中通过 CDN 引入)
|
||
|
||
(function () {
|
||
function initGlobe() {
|
||
var container = document.getElementById("bannerGlobe");
|
||
if (!container) {
|
||
console.warn("[Globe] bannerGlobe container not found");
|
||
return;
|
||
}
|
||
if (typeof THREE === "undefined") {
|
||
console.warn(
|
||
"[Globe] THREE is undefined, please ensure three.min.js is loaded"
|
||
);
|
||
return;
|
||
}
|
||
if (typeof ThreeGlobe === "undefined") {
|
||
console.warn(
|
||
"[Globe] ThreeGlobe is undefined, please ensure three-globe.min.js is loaded"
|
||
);
|
||
return;
|
||
}
|
||
|
||
var width = container.clientWidth || 400;
|
||
var height = container.clientHeight || 400;
|
||
|
||
// 基础 Three.js 场景
|
||
var scene = new THREE.Scene();
|
||
|
||
var camera = new THREE.PerspectiveCamera(40, width / height, 0.1, 1000);
|
||
// 稍微偏右俯视一点,拉远一点避免球体被裁剪
|
||
camera.position.set(0, 0.3, 4.8);
|
||
camera.lookAt(0, 0, 0);
|
||
|
||
var renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
||
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
|
||
renderer.setSize(width, height);
|
||
renderer.setClearColor(0x000000, 0); // 透明背景
|
||
container.appendChild(renderer.domElement);
|
||
|
||
// 柔和光照
|
||
var ambient = new THREE.AmbientLight(0xffffff, 0.65);
|
||
scene.add(ambient);
|
||
var dirLight = new THREE.DirectionalLight(0xffffff, 0.9);
|
||
dirLight.position.set(5, 3, 5);
|
||
scene.add(dirLight);
|
||
|
||
// 示例节点数据(可后续替换为真实机房经纬度)
|
||
var nodes = [
|
||
{ name: "Beijing", lat: 39.9, lng: 116.4 },
|
||
{ name: "Shanghai", lat: 31.2, lng: 121.5 },
|
||
{ name: "Hong Kong", lat: 22.5, lng: 114.1 },
|
||
{ name: "Singapore", lat: 1.3, lng: 103.8 },
|
||
{ name: "San Francisco", lat: 37.8, lng: -122.4 },
|
||
{ name: "Berlin", lat: 52.5, lng: 13.4 },
|
||
{ name: "Tokyo", lat: 35.7, lng: 139.7 },
|
||
];
|
||
|
||
// 示例 arcs:从北京发出到其他节点
|
||
var arcs = nodes
|
||
.filter(function (n) {
|
||
return n.name !== "Beijing";
|
||
})
|
||
.map(function (n) {
|
||
return {
|
||
startLat: 39.9,
|
||
startLng: 116.4,
|
||
endLat: n.lat,
|
||
endLng: n.lng,
|
||
};
|
||
});
|
||
|
||
// three-globe 实例:使用大气层 + 节点 + 飞线 + 空心陆地多边形
|
||
var globe = new ThreeGlobe({
|
||
waitForGlobeReady: true,
|
||
animateIn: true,
|
||
})
|
||
// 不使用 three-globe 内置球体贴图,只保留大气层,陆地轮廓由 polygons 层绘制
|
||
.showGlobe(false)
|
||
.showAtmosphere(false)
|
||
.atmosphereColor("#2b9fff")
|
||
.atmosphereAltitude(0.18)
|
||
.showGraticules(false)
|
||
// 节点柱状体
|
||
.pointsData(nodes)
|
||
.pointColor(function () {
|
||
return "#5ad8ff";
|
||
})
|
||
.pointAltitude(0.06)
|
||
.pointRadius(0.32)
|
||
.pointsTransitionDuration(1000)
|
||
// 弧线
|
||
.arcsData(arcs)
|
||
.arcColor(function () {
|
||
return ["#5ad8ff", "#ffffff"];
|
||
})
|
||
.arcAltitude(0.35)
|
||
.arcStroke(0.45)
|
||
.arcDashLength(0.6)
|
||
.arcDashGap(0.25)
|
||
.arcDashAnimateTime(2600);
|
||
|
||
scene.add(globe);
|
||
|
||
// 按 hollow-globe 风格加载陆地多边形,绘制空心地球轮廓
|
||
(function loadLandPolygons() {
|
||
if (typeof topojson === "undefined") {
|
||
console.warn(
|
||
"[Globe] topojson-client is undefined, unable to render hollow globe polygons"
|
||
);
|
||
return;
|
||
}
|
||
|
||
// 请确保 /web/BlackFruit-web/assets/data/land-110m.json 存在,
|
||
// 内容为你刚才提供的 Topology JSON。
|
||
fetch("/web/BlackFruit-web/assets/data/land-110m.json")
|
||
.then(function (res) {
|
||
return res.json();
|
||
})
|
||
.then(function (landTopo) {
|
||
try {
|
||
var landGeo = topojson.feature(
|
||
landTopo,
|
||
landTopo.objects.land
|
||
).features;
|
||
|
||
globe
|
||
.polygonsData(landGeo)
|
||
.polygonCapMaterial(
|
||
new THREE.MeshLambertMaterial({
|
||
color: "darkslategrey",
|
||
side: THREE.DoubleSide,
|
||
})
|
||
)
|
||
.polygonSideColor(function () {
|
||
return "rgba(0,0,0,0)";
|
||
});
|
||
} catch (e) {
|
||
console.warn("[Globe] failed to parse land-110m topology:", e);
|
||
}
|
||
})
|
||
.catch(function (err) {
|
||
console.warn(
|
||
"[Globe] failed to load /web/BlackFruit-web/assets/data/land-110m.json:",
|
||
err
|
||
);
|
||
});
|
||
})();
|
||
|
||
// 暴露给调试用
|
||
if (typeof window !== "undefined") {
|
||
window.__globe = globe;
|
||
}
|
||
|
||
// 星辰背景(简单加一点点空间感)
|
||
var starGeometry = new THREE.BufferGeometry();
|
||
var starCount = 600;
|
||
var starPositions = new Float32Array(starCount * 3);
|
||
for (var i = 0; i < starCount; i++) {
|
||
var sr = 10 + Math.random() * 5;
|
||
var theta = Math.random() * Math.PI * 2;
|
||
var phi = Math.acos(2 * Math.random() - 1);
|
||
var sx = sr * Math.sin(phi) * Math.cos(theta);
|
||
var sy = sr * Math.sin(phi) * Math.sin(theta);
|
||
var sz = sr * Math.cos(phi);
|
||
starPositions[i * 3] = sx;
|
||
starPositions[i * 3 + 1] = sy;
|
||
starPositions[i * 3 + 2] = sz;
|
||
}
|
||
starGeometry.setAttribute(
|
||
"position",
|
||
new THREE.BufferAttribute(starPositions, 3)
|
||
);
|
||
var starMaterial = new THREE.PointsMaterial({
|
||
color: 0x3f76ff,
|
||
size: 0.08,
|
||
transparent: true,
|
||
opacity: 0.7,
|
||
});
|
||
var stars = new THREE.Points(starGeometry, starMaterial);
|
||
scene.add(stars);
|
||
|
||
// 自适应窗口大小
|
||
function onResize() {
|
||
if (!container) return;
|
||
var w = container.clientWidth || width;
|
||
var h = container.clientHeight || height;
|
||
camera.aspect = w / h;
|
||
camera.updateProjectionMatrix();
|
||
renderer.setSize(w, h);
|
||
}
|
||
window.addEventListener("resize", onResize);
|
||
|
||
// 动画循环
|
||
var lastTime = performance.now();
|
||
function animate() {
|
||
requestAnimationFrame(animate);
|
||
|
||
var now = performance.now();
|
||
var delta = (now - lastTime) / 1000;
|
||
lastTime = now;
|
||
|
||
// 地球自转
|
||
globe.rotation.y += delta * 0.25;
|
||
// 星空轻微旋转
|
||
stars.rotation.y -= delta * 0.03;
|
||
|
||
renderer.render(scene, camera);
|
||
}
|
||
|
||
animate();
|
||
}
|
||
|
||
if (document.readyState === "loading") {
|
||
document.addEventListener("DOMContentLoaded", initGlobe);
|
||
} else {
|
||
initGlobe();
|
||
}
|
||
})();
|