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.
98 lines
3.4 KiB
JavaScript
98 lines
3.4 KiB
JavaScript
import * as THREE from 'three';
|
|
import { ENTRIES } from '../data/entries.js';
|
|
import { getPageTextures } from '../textures/pages.js';
|
|
import { makeTextSprite } from '../utils/labels.js';
|
|
|
|
export const TIMELINE_SPREAD = 6.2; // 横轴范围 ±6.2(超出视口,可横向滚动)
|
|
|
|
const CARD_FLOAT = 0.14; // 选中时上浮高度
|
|
const NAME_Y = 0.42; // 主题标签基准高度(活页上方)
|
|
|
|
function cardX(i, n) {
|
|
return (i / (n - 1)) * 2 * TIMELINE_SPREAD - TIMELINE_SPREAD;
|
|
}
|
|
|
|
export class Timeline {
|
|
constructor() {
|
|
this.group = new THREE.Group();
|
|
this.cards = [];
|
|
this.selected = -1;
|
|
|
|
this._buildLine();
|
|
this._buildCards();
|
|
}
|
|
|
|
_buildLine() {
|
|
const n = ENTRIES.length;
|
|
const min = cardX(0, n) - 0.4;
|
|
const max = cardX(n - 1, n) + 0.4;
|
|
|
|
const lineMat = new THREE.LineBasicMaterial({ color: 0xc9a227, transparent: true, opacity: 0.7 });
|
|
const lineGeo = new THREE.BufferGeometry().setFromPoints([
|
|
new THREE.Vector3(min, -0.85, 0), new THREE.Vector3(max, -0.85, 0),
|
|
]);
|
|
this.group.add(new THREE.Line(lineGeo, lineMat));
|
|
|
|
const glowMat = new THREE.LineBasicMaterial({ color: 0xe0c46a, transparent: true, opacity: 0.22 });
|
|
const glowGeo = new THREE.BufferGeometry().setFromPoints([
|
|
new THREE.Vector3(min, -0.855, -0.03), new THREE.Vector3(max, -0.855, -0.03),
|
|
]);
|
|
this.group.add(new THREE.Line(glowGeo, glowMat));
|
|
|
|
// 刻度:向上指向卡片、向下指向时间标签
|
|
const tickMat = new THREE.LineBasicMaterial({ color: 0xc9a227, transparent: true, opacity: 0.5 });
|
|
ENTRIES.forEach((e, i) => {
|
|
const x = cardX(i, n);
|
|
const g = new THREE.BufferGeometry().setFromPoints([
|
|
new THREE.Vector3(x, -0.98, 0), new THREE.Vector3(x, -0.7, 0),
|
|
]);
|
|
this.group.add(new THREE.Line(g, tickMat));
|
|
});
|
|
}
|
|
|
|
_buildCards() {
|
|
const n = ENTRIES.length;
|
|
const cardGeo = new THREE.PlaneGeometry(0.4, 0.565);
|
|
|
|
ENTRIES.forEach((entry, i) => {
|
|
const x = cardX(i, n);
|
|
const { front } = getPageTextures(entry);
|
|
|
|
const mat = new THREE.MeshStandardMaterial({ map: front, roughness: 0.85, metalness: 0.0 });
|
|
const card = new THREE.Mesh(cardGeo, mat);
|
|
card.position.set(x, 0, 0);
|
|
card.userData = { view: 'timeline', index: i };
|
|
card.castShadow = true;
|
|
|
|
const outline = new THREE.Mesh(
|
|
new THREE.PlaneGeometry(0.4 * 1.1, 0.565 * 1.1),
|
|
new THREE.MeshBasicMaterial({ color: 0xc9a227, transparent: true, opacity: 0.9, side: THREE.BackSide })
|
|
);
|
|
outline.position.z = -0.01;
|
|
outline.visible = false;
|
|
card.add(outline);
|
|
|
|
// 活页上方:主题(常显,随选中上浮,超宽居中换行)
|
|
const name = makeTextSprite(entry.name, { fontSize: 17, color: '#e8d9b4', scale: 0.13, maxWidth: 1.0 });
|
|
name.position.set(x, NAME_Y, 0);
|
|
|
|
// 轴下方:时间(常显,固定不动)
|
|
const time = makeTextSprite(entry.time, { fontSize: 13, color: '#c9a227', scale: 0.11 });
|
|
time.position.set(x, -1.06, 0);
|
|
|
|
this.group.add(card, name, time);
|
|
this.cards.push({ card, outline, name, nameBaseY: NAME_Y, index: i });
|
|
});
|
|
}
|
|
|
|
setSelected(index) {
|
|
this.selected = index;
|
|
this.cards.forEach((c) => {
|
|
const on = c.index === index;
|
|
c.outline.visible = on;
|
|
c.card.position.y = on ? CARD_FLOAT : 0;
|
|
c.name.position.y = c.nameBaseY + (on ? CARD_FLOAT : 0);
|
|
});
|
|
}
|
|
}
|