Replace Three.js globe with Cesium
Migrate the globe view to a Cesium viewer with Esri satellite imagery and terrain, dropping the custom blue-marble texture and dot-bead rendering. Add vite-plugin-cesium and a container element for the viewer. Also rework the binder leather and suede materials with dedicated normal maps, and refine timeline labels and page hole sizing.
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
import * as Cesium from 'cesium';
|
||||
import { ENTRIES, CATEGORY_INFO } from '../data/entries.js';
|
||||
|
||||
// 免费、无需 Ion 令牌的影像与地形源
|
||||
const IMAGERY_URL =
|
||||
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}';
|
||||
const TERRAIN_URL =
|
||||
'https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer';
|
||||
|
||||
export class CesiumGlobe {
|
||||
constructor({ onPick } = {}) {
|
||||
this.onPick = onPick;
|
||||
this.viewer = null;
|
||||
this.markers = [];
|
||||
this.selected = -1;
|
||||
this._created = false;
|
||||
this._visible = false;
|
||||
}
|
||||
|
||||
ensureCreated() {
|
||||
if (this._created) return;
|
||||
this._created = true;
|
||||
|
||||
const container = document.getElementById('cesium-container');
|
||||
|
||||
this.viewer = new Cesium.Viewer(container, {
|
||||
animation: false,
|
||||
timeline: false,
|
||||
geocoder: false,
|
||||
homeButton: false,
|
||||
sceneModePicker: false,
|
||||
baseLayerPicker: false,
|
||||
navigationHelpButton: false,
|
||||
fullscreenButton: false,
|
||||
infoBox: false,
|
||||
selectionIndicator: false,
|
||||
baseLayer: false,
|
||||
requestRenderMode: true,
|
||||
});
|
||||
|
||||
const viewer = this.viewer;
|
||||
const scene = viewer.scene;
|
||||
|
||||
// 贴合暗色主题
|
||||
scene.backgroundColor = Cesium.Color.fromCssColorString('#14100d');
|
||||
scene.skyAtmosphere.show = false;
|
||||
scene.skyBox.show = false;
|
||||
scene.sun.show = false;
|
||||
scene.moon.show = false;
|
||||
scene.globe.enableLighting = false;
|
||||
scene.globe.showGroundAtmosphere = false;
|
||||
scene.globe.baseColor = Cesium.Color.fromCssColorString('#0a1626');
|
||||
|
||||
// 相机交互:限制滚轮缩放范围,避免过近/过远
|
||||
const controller = scene.screenSpaceCameraController;
|
||||
controller.enableCollisionDetection = true; // 必须开启:缩放距离限制在关闭时会被忽略
|
||||
controller.minimumZoomDistance = 10000; // 最近约 10 km(区域尺度)
|
||||
controller.maximumZoomDistance = 8e6; // 最远约 8000 km
|
||||
|
||||
// 精细化渲染:质量与性能的平衡
|
||||
scene.globe.maximumScreenSpaceError = 2.0; // 默认细分,最省性能
|
||||
scene.globe.tileCacheSize = 600; // 更大的瓦片缓存,减少细节刷新
|
||||
scene.fxaa = false; // 关闭抗锯齿后处理,省一个全屏通道
|
||||
viewer.resolutionScale = 1.5; // 1.5 倍超采样,替代 FXAA 提供更锐利的抗锯齿
|
||||
scene.maximumRenderTimeChange = Infinity; // 配合 requestRenderMode:静止时停止重绘,大幅省电省帧
|
||||
|
||||
// 卫星影像
|
||||
const imagery = new Cesium.UrlTemplateImageryProvider({
|
||||
url: IMAGERY_URL,
|
||||
credit: 'Imagery © Esri, Maxar, Earthstar Geographics',
|
||||
maximumLevel: 19,
|
||||
});
|
||||
viewer.imageryLayers.addImageryProvider(imagery);
|
||||
|
||||
// 真实地形(Esri World Elevation 3D,免费无 token;失败则保持默认椭球)
|
||||
const terrain = new Cesium.Terrain(
|
||||
Cesium.ArcGISTiledElevationTerrainProvider.fromUrl(TERRAIN_URL),
|
||||
);
|
||||
terrain.errorEvent.addEventListener((err) => {
|
||||
console.warn('[CesiumGlobe] 地形加载失败,使用椭球:', err);
|
||||
});
|
||||
scene.setTerrain(terrain);
|
||||
|
||||
viewer.clock.shouldAnimate = false;
|
||||
|
||||
this._buildMarkers();
|
||||
this._setupPick();
|
||||
|
||||
viewer.camera.setView({
|
||||
destination: Cesium.Cartesian3.fromDegrees(35, 12, 7.2e6),
|
||||
});
|
||||
}
|
||||
|
||||
_buildMarkers() {
|
||||
const viewer = this.viewer;
|
||||
const gold = Cesium.Color.fromCssColorString('#e0c46a');
|
||||
const ivory = Cesium.Color.fromCssColorString('#e8d9b4');
|
||||
const ink = Cesium.Color.fromCssColorString('#14100d');
|
||||
|
||||
ENTRIES.forEach((entry, i) => {
|
||||
const info = CATEGORY_INFO[entry.category] || CATEGORY_INFO['事'];
|
||||
const color = Cesium.Color.fromCssColorString(info.color);
|
||||
|
||||
const entity = viewer.entities.add({
|
||||
position: Cesium.Cartesian3.fromDegrees(entry.lng, entry.lat),
|
||||
point: {
|
||||
pixelSize: 8,
|
||||
color,
|
||||
outlineColor: gold,
|
||||
outlineWidth: 1.5,
|
||||
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
||||
},
|
||||
label: {
|
||||
text: entry.name,
|
||||
font: '17px Georgia, "Noto Serif SC", "Songti SC", "Microsoft YaHei", serif',
|
||||
fillColor: ivory,
|
||||
outlineColor: ink,
|
||||
outlineWidth: 3,
|
||||
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
||||
pixelOffset: new Cesium.Cartesian2(0, -16),
|
||||
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
||||
show: false,
|
||||
},
|
||||
});
|
||||
|
||||
entity.entryIndex = i;
|
||||
this.markers.push({ entity, index: i });
|
||||
});
|
||||
}
|
||||
|
||||
_setupPick() {
|
||||
const handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas);
|
||||
handler.setInputAction((movement) => {
|
||||
const picked = this.viewer.scene.pick(movement.position);
|
||||
if (Cesium.defined(picked) && picked.id && picked.id.entryIndex !== undefined) {
|
||||
this.onPick?.(picked.id.entryIndex);
|
||||
}
|
||||
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
||||
this._handler = handler;
|
||||
}
|
||||
|
||||
setSelected(index) {
|
||||
this.selected = index;
|
||||
if (!this.viewer) return;
|
||||
this.markers.forEach(({ entity, index: i }) => {
|
||||
const on = i === index;
|
||||
entity.point.pixelSize = on ? 12 : 8;
|
||||
entity.label.show = on;
|
||||
});
|
||||
this.viewer.scene.requestRender();
|
||||
}
|
||||
|
||||
setVisible(visible) {
|
||||
this._visible = visible;
|
||||
if (visible && this.viewer) this.viewer.scene.requestRender();
|
||||
}
|
||||
|
||||
resize() {
|
||||
if (this.viewer) this.viewer.resize();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user