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:
2026-09-18 21:29:30 +08:00
parent ac12405bdf
commit 13d3ef180a
14 changed files with 1281 additions and 384 deletions
+41 -11
View File
@@ -3,13 +3,13 @@ import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { ENTRIES } from '../data/entries.js';
import { Binder } from '../scene/binder.js';
import { Timeline, TIMELINE_SPREAD } from '../scene/timeline.js';
import { Globe } from '../scene/globe.js';
import { CesiumGlobe } from '../scene/CesiumGlobe.js';
import { Overlay } from '../ui/overlay.js';
import { clamp, easeInOutCubic } from '../utils/easing.js';
const CAMERA_POSES = {
binder: { pos: new THREE.Vector3(0.2, 0.75, 2.95), target: new THREE.Vector3(0, 0.02, 0), max: 6, min: 1.6 },
timeline: { pos: new THREE.Vector3(0, 1.4, 7.9), target: new THREE.Vector3(0, 0, 0), max: 13, min: 3.4 },
timeline: { pos: new THREE.Vector3(0, 1.4, 7.9), target: new THREE.Vector3(0, 0, 0), max: 13, min: 1.5 },
globe: { pos: new THREE.Vector3(0, 0.35, 3.7), target: new THREE.Vector3(0, 0, 0), max: 8, min: 2.2 },
};
@@ -69,7 +69,7 @@ export class App {
}
_initLights() {
const hemi = new THREE.HemisphereLight(0xfff1d8, 0x221811, 0.55);
const hemi = new THREE.HemisphereLight(0xfff1d8, 0x2a1f14, 0.8);
this.scene.add(hemi);
const key = new THREE.DirectionalLight(0xffe6c0, 2.4);
@@ -89,6 +89,16 @@ export class App {
const rim = new THREE.DirectionalLight(0x8fa0d0, 0.7);
rim.position.set(-2.5, 1.2, -3.5);
this.scene.add(rim);
// 低角度掠射光:横扫皮面,凸显皮革纹理凹凸
const graze = new THREE.DirectionalLight(0xffd9a8, 1.1);
graze.position.set(-4.5, 0.7, 2.6);
this.scene.add(graze);
// 背面补光:让外侧牛皮从背面看也可见
const backLight = new THREE.DirectionalLight(0xffd9a8, 0.55);
backLight.position.set(0.5, 1.2, -3.5);
this.scene.add(backLight);
}
_initScene() {
@@ -96,16 +106,14 @@ export class App {
this.binder.onChange = (i) => this._select(i, { syncBinder: false });
this.timeline = new Timeline();
this.globe = new Globe({ renderer: this.renderer });
this.globe = new CesiumGlobe({ onPick: (i) => this._pickGlobe(i) });
this.scene.add(this.binder.group);
this.scene.add(this.timeline.group);
this.scene.add(this.globe.group);
this._buildGround();
this.timeline.group.visible = false;
this.globe.group.visible = false;
this._collectInteractives();
}
@@ -148,7 +156,7 @@ export class App {
});
});
this.timeline.cards.forEach((c) => this.interactives.timeline.push(c.card));
this.globe.markers.forEach((m) => this.interactives.globe.push(m.marker));
// 地理视图由 Cesium 自行拾取,不参与 Three.js 射线检测
}
_initEvents() {
@@ -237,6 +245,13 @@ export class App {
return hits.length ? hits[0] : null;
}
_pickGlobe(index) {
this._select(index, { syncBinder: false });
// 点击地理标记时,显示右下角登记卡详情
document.getElementById('detail-card').classList.remove('hidden');
this.binder.syncTo(index);
}
_pick() {
const hit = this._hitTest();
if (!hit) return;
@@ -280,9 +295,24 @@ export class App {
this.view = view;
this.overlay.setView(view);
const isGlobe = view === 'globe';
this.binder.group.visible = view === 'binder';
this.timeline.group.visible = view === 'timeline';
this.globe.group.visible = view === 'globe';
// Cesium 地球:地理视图显示 Cesium 画布,其余视图显示 Three.js 画布
document.getElementById('scene').style.display = isGlobe ? 'none' : 'block';
document.getElementById('cesium-container').style.display = isGlobe ? 'block' : 'none';
if (isGlobe) {
this.globe.ensureCreated();
this.globe.setVisible(true);
this.globe.setSelected(this.selected);
this.globe.resize();
} else {
this.globe.setVisible(false);
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
// 登记卡详情仅在活页本视图展示,避免遮挡时间轴/地理内容
document.getElementById('detail-card').classList.toggle('hidden', view !== 'binder');
@@ -295,8 +325,7 @@ export class App {
this.controls.enableRotate = !locked;
this.controls.enablePan = !locked;
this.controls.enableZoom = true;
this.controls.autoRotate = view === 'globe';
this.controls.autoRotateSpeed = 0.5;
this.controls.autoRotate = false;
// 进入时间轴时复位横向偏移
if (view === 'timeline') {
@@ -304,7 +333,7 @@ export class App {
this.timeline.group.position.x = 0;
}
this._tweenCamera(pose.pos, pose.target);
if (!isGlobe) this._tweenCamera(pose.pos, pose.target);
}
_tweenCamera(toPos, toTarget) {
@@ -320,6 +349,7 @@ export class App {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
if (this.globe) this.globe.resize();
}
_animate() {