|
@@ -46,7 +46,7 @@ export default {
|
|
|
name: "GlobeTransition",
|
|
name: "GlobeTransition",
|
|
|
props: {
|
|
props: {
|
|
|
// 总时长:默认约 3.8s(含 0.5s 俯冲 + 同步渐黑)
|
|
// 总时长:默认约 3.8s(含 0.5s 俯冲 + 同步渐黑)
|
|
|
- durationMs: { type: Number, default: 3800 },
|
|
|
|
|
|
|
+ durationMs: { type: Number, default: 5000 },
|
|
|
// 系统可传入 { lon, lat, name },不传则默认北京·通州
|
|
// 系统可传入 { lon, lat, name },不传则默认北京·通州
|
|
|
target: {
|
|
target: {
|
|
|
type: Object,
|
|
type: Object,
|
|
@@ -80,6 +80,7 @@ export default {
|
|
|
|
|
|
|
|
// china outline on globe
|
|
// china outline on globe
|
|
|
chinaLine: null,
|
|
chinaLine: null,
|
|
|
|
|
+ chinaGlowLine: null,
|
|
|
|
|
|
|
|
// stage states
|
|
// stage states
|
|
|
fading: false,
|
|
fading: false,
|
|
@@ -281,6 +282,17 @@ export default {
|
|
|
if (this.chinaLine) {
|
|
if (this.chinaLine) {
|
|
|
this.chinaLine.visible = false;
|
|
this.chinaLine.visible = false;
|
|
|
group.add(this.chinaLine);
|
|
group.add(this.chinaLine);
|
|
|
|
|
+
|
|
|
|
|
+ // 额外发光层:复制一层略微放大,形成更明显的“粗亮边界”
|
|
|
|
|
+ const glowLine = this.chinaLine.clone();
|
|
|
|
|
+ glowLine.material = this.chinaLine.material.clone();
|
|
|
|
|
+ glowLine.material.opacity = 0.0;
|
|
|
|
|
+ glowLine.material.blending = THREE.AdditiveBlending;
|
|
|
|
|
+ glowLine.material.depthWrite = false;
|
|
|
|
|
+ glowLine.scale.setScalar(1.006); // 避免 z-fighting
|
|
|
|
|
+ glowLine.visible = false;
|
|
|
|
|
+ group.add(glowLine);
|
|
|
|
|
+ this.chinaGlowLine = glowLine;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Label (HTML)
|
|
// Label (HTML)
|
|
@@ -306,9 +318,10 @@ export default {
|
|
|
const t = (now - this.t0) / 1000; // seconds
|
|
const t = (now - this.t0) / 1000; // seconds
|
|
|
const totalBase = (this.durationMs || 3800) / 1000;
|
|
const totalBase = (this.durationMs || 3800) / 1000;
|
|
|
// 为了避免俯冲后暴露网格线细节:强制将总时长收敛到短过渡(可按需提高上限)
|
|
// 为了避免俯冲后暴露网格线细节:强制将总时长收敛到短过渡(可按需提高上限)
|
|
|
- const total = Math.min(totalBase, 4.5);
|
|
|
|
|
|
|
+ const HOLD_EXTRA = 0.5; // 中国地图停留额外增加 0.5s
|
|
|
|
|
+ const baseTotal = Math.min(totalBase, 4.5);
|
|
|
|
|
+ const total = baseTotal + HOLD_EXTRA;
|
|
|
const p = Math.min(1, t / total);
|
|
const p = Math.min(1, t / total);
|
|
|
-
|
|
|
|
|
// --- Timeline (short dive + fade) ---
|
|
// --- Timeline (short dive + fade) ---
|
|
|
// 目标节奏:
|
|
// 目标节奏:
|
|
|
// 0~2.5s :旋转展示 + 平滑转向目标(无停顿)
|
|
// 0~2.5s :旋转展示 + 平滑转向目标(无停顿)
|
|
@@ -317,8 +330,9 @@ export default {
|
|
|
const A = Math.min(2.5, Math.max(1.8, total - 1.3)); // 尽量保持 2.5s(总时长过短时自适应)
|
|
const A = Math.min(2.5, Math.max(1.8, total - 1.3)); // 尽量保持 2.5s(总时长过短时自适应)
|
|
|
const T1 = A * 0.45; // 旋转展示段结束
|
|
const T1 = A * 0.45; // 旋转展示段结束
|
|
|
const T2 = A; // 转向目标结束(到达正面)
|
|
const T2 = A; // 转向目标结束(到达正面)
|
|
|
- const T3 = Math.min(T2 + 0.8, total - 0.5); // HUD 结束(约0.8s),确保给俯冲留 0.5s
|
|
|
|
|
- const T4 = total; // 结束(俯冲尾段同步渐黑)
|
|
|
|
|
|
|
+ const T3Base = Math.min(T2 + 0.8, baseTotal - 0.5); // 原 HUD 段(约0.8s),给俯冲留 0.5s
|
|
|
|
|
+ const T3 = T3Base + HOLD_EXTRA; // 延长停留后再俯冲
|
|
|
|
|
+ const T4 = total; // 结束(俯冲尾段同步渐黑)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -346,15 +360,6 @@ export default {
|
|
|
// 更快淡出网格线
|
|
// 更快淡出网格线
|
|
|
this.wire.material.opacity = base * Math.pow(1 - fadeK, 2.4);
|
|
this.wire.material.opacity = base * Math.pow(1 - fadeK, 2.4);
|
|
|
}
|
|
}
|
|
|
- if (this.chinaLine && this.chinaLine.material) {
|
|
|
|
|
- const base = this.chinaLine.material.__baseOpacity ?? this.chinaLine.material.opacity;
|
|
|
|
|
- this.chinaLine.material.__baseOpacity = base;
|
|
|
|
|
- this.chinaLine.material.opacity = base * (1 - fadeK);
|
|
|
|
|
- }
|
|
|
|
|
- if (this.label && this.label.el) {
|
|
|
|
|
- const cur = Number(this.label.el.style.opacity || 1);
|
|
|
|
|
- this.label.el.style.opacity = String(cur * (1 - fadeK));
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -367,35 +372,114 @@ export default {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ----- Lock phase: bring target to front -----
|
|
// ----- Lock phase: bring target to front -----
|
|
|
- // 平滑把目标点“转到屏幕前方”(避免突然加速)
|
|
|
|
|
- const lockK = easeInOutCubic(p2);
|
|
|
|
|
- if (this.earthGroup) {
|
|
|
|
|
- // 进入锁定段时记录起始四元数,只记录一次
|
|
|
|
|
- if (t >= T1 && t < T2 && !this.lockInit) {
|
|
|
|
|
- this.lockStartQuat = this.earthGroup.quaternion.clone();
|
|
|
|
|
- this.lockInit = true;
|
|
|
|
|
- }
|
|
|
|
|
- if (t < T1) this.lockInit = false;
|
|
|
|
|
-
|
|
|
|
|
- const targetDir = this.targetLocal.clone().normalize();
|
|
|
|
|
- const front = new THREE.Vector3(0, 0, 1);
|
|
|
|
|
- const targetQ = new THREE.Quaternion().setFromUnitVectors(targetDir, front);
|
|
|
|
|
|
|
+// 平滑把目标点“转到屏幕前方”(避免突然加速)
|
|
|
|
|
+// 并在“展示中国地图阶段”(T2~T3) 额外绕屏幕朝向(front)做一次旋转,
|
|
|
|
|
+// 用来把中国轮廓从“倒着/歪着”调整为更正面的观感。
|
|
|
|
|
+const lockK = easeInOutCubic(p2);
|
|
|
|
|
+const extraK = easeInOutCubic(p3);
|
|
|
|
|
+
|
|
|
|
|
+// 额外旋转角度(度):如果方向不对,把 90 改成 -90;如果还是倒着可试 180
|
|
|
|
|
+const EXTRA_ROT_DEG = 120;
|
|
|
|
|
+
|
|
|
|
|
+if (this.earthGroup) {
|
|
|
|
|
+ // 进入锁定段时记录起始四元数,只记录一次
|
|
|
|
|
+ if (t >= T1 && t < T2 && !this.lockInit) {
|
|
|
|
|
+ this.lockStartQuat = this.earthGroup.quaternion.clone();
|
|
|
|
|
+ this.lockInit = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (t < T1) this.lockInit = false;
|
|
|
|
|
+
|
|
|
|
|
+ const targetDir = this.targetLocal.clone().normalize();
|
|
|
|
|
+ const front = new THREE.Vector3(0, 0, 1);
|
|
|
|
|
+
|
|
|
|
|
+ // 基础锁定:目标点居中到屏幕正前方
|
|
|
|
|
+ const baseTargetQ = new THREE.Quaternion().setFromUnitVectors(targetDir, front);
|
|
|
|
|
+
|
|
|
|
|
+ if (t < T2) {
|
|
|
|
|
+ // 锁定段:start -> baseTargetQ
|
|
|
|
|
+ const startQ = this.lockStartQuat ? this.lockStartQuat : this.earthGroup.quaternion.clone();
|
|
|
|
|
+ const q = startQ.clone();
|
|
|
|
|
+ q.slerp(baseTargetQ, lockK);
|
|
|
|
|
+ this.earthGroup.quaternion.copy(q);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 展示中国阶段:在 baseTargetQ 基础上叠加一次额外旋转(不破坏“目标点居中”)
|
|
|
|
|
+ const extraAngle = THREE.MathUtils.degToRad(EXTRA_ROT_DEG) * extraK;
|
|
|
|
|
+ const qExtra = new THREE.Quaternion().setFromAxisAngle(front, extraAngle);
|
|
|
|
|
+
|
|
|
|
|
+ // qFinal = qExtra * baseTargetQ
|
|
|
|
|
+ const qFinal = baseTargetQ.clone().premultiply(qExtra);
|
|
|
|
|
+ this.earthGroup.quaternion.copy(qFinal);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- const startQ = this.lockStartQuat ? this.lockStartQuat : this.earthGroup.quaternion.clone();
|
|
|
|
|
- const q = startQ.clone();
|
|
|
|
|
- q.slerp(targetQ, lockK);
|
|
|
|
|
- this.earthGroup.quaternion.copy(q);
|
|
|
|
|
|
|
+ // ----- China outline glow during lock/HUD/dive -----
|
|
|
|
|
+ // 仪式感曲线:先快速点亮 -> 稳定发光 -> 进入俯冲前轻微收束 -> 俯冲渐黑淡出
|
|
|
|
|
+ // 说明:
|
|
|
|
|
+ // - T1~T2:轮廓逐步显现(跟随锁定)
|
|
|
|
|
+ // - T2 附近:快速“点亮/爆闪”并回落到稳定强度
|
|
|
|
|
+ // - T3 前:轻微收束,避免俯冲近景时太“刺”
|
|
|
|
|
+ // - T3~T4:由 fadeK 控制统一淡出
|
|
|
|
|
+ const chinaVis = t >= T1 && t <= T4;
|
|
|
|
|
+ const fadeMult = 1 - fadeK;
|
|
|
|
|
+
|
|
|
|
|
+ // 分段关键点
|
|
|
|
|
+ const flashStart = T2 - 0.12;
|
|
|
|
|
+ const flashEnd = T2 + 0.18;
|
|
|
|
|
+ const settleEnd = T2 + 0.48;
|
|
|
|
|
+ const preFadeStart = Math.max(T2 + 0.6, T3 - 0.22);
|
|
|
|
|
+
|
|
|
|
|
+ // 1) 基础显现(随锁定)
|
|
|
|
|
+ const appearK = easeOutCubic(clamp01((t - T1) / Math.max(0.0001, (flashStart - T1))));
|
|
|
|
|
+ // 2) 快速点亮(爆闪)
|
|
|
|
|
+ const flashK = easeOutExpo(clamp01((t - flashStart) / Math.max(0.0001, (flashEnd - flashStart))));
|
|
|
|
|
+ // 3) 回落到稳定(避免一直过曝)
|
|
|
|
|
+ const settleK = easeOutCubic(clamp01((t - flashEnd) / Math.max(0.0001, (settleEnd - flashEnd))));
|
|
|
|
|
+ // 4) 俯冲前轻微收束
|
|
|
|
|
+ const preFadeK = easeInOutCubic(clamp01((t - preFadeStart) / Math.max(0.0001, (T3 - preFadeStart))));
|
|
|
|
|
+
|
|
|
|
|
+ // 组合强度(>1 允许短暂过曝,然后回落)
|
|
|
|
|
+ let intensity = 0;
|
|
|
|
|
+ if (t < T1) {
|
|
|
|
|
+ intensity = 0;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 先从 0 -> 0.7
|
|
|
|
|
+ intensity = 0.7 * appearK;
|
|
|
|
|
+ // 点亮爆闪:额外 +0.75(短促)
|
|
|
|
|
+ intensity += 0.75 * flashK;
|
|
|
|
|
+ // 回落:从 1.45 -> 1.0
|
|
|
|
|
+ const over = 1.45;
|
|
|
|
|
+ const stable = 1.0;
|
|
|
|
|
+ const target = over + (stable - over) * settleK;
|
|
|
|
|
+ intensity = Math.min(intensity, target);
|
|
|
|
|
+ // 稳定呼吸(弱)
|
|
|
|
|
+ const breathe = 0.94 + 0.06 * Math.sin(now * 0.0026);
|
|
|
|
|
+ intensity *= breathe;
|
|
|
|
|
+ // 俯冲前收束到 0.88
|
|
|
|
|
+ intensity *= (1 - 0.12 * preFadeK);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // ----- China outline glow during lock/HUD/dive ----- during lock/HUD/dive -----
|
|
|
|
|
- const chinaVis = t >= T1 && t <= T4;
|
|
|
|
|
- if (this.chinaLine) {
|
|
|
|
|
|
|
+ // 短暂“电流脉冲”附加:定位成功的仪式感(衰减振荡,保证不抖)
|
|
|
|
|
+ const dt = t - T2;
|
|
|
|
|
+ const shock = dt > 0 ? Math.max(0, Math.sin(dt * Math.PI * 3.0)) * Math.exp(-dt / 0.55) : 0;
|
|
|
|
|
+ intensity += 0.18 * shock;
|
|
|
|
|
+
|
|
|
|
|
+ // 统一淡出(俯冲渐黑)
|
|
|
|
|
+ intensity *= fadeMult;
|
|
|
|
|
+ // 防止数值越界
|
|
|
|
|
+ intensity = Math.max(0, Math.min(1.6, intensity));
|
|
|
|
|
+
|
|
|
|
|
+ // 主线/发光层输出
|
|
|
|
|
+ const mainMax = 0.98;
|
|
|
|
|
+ const glowMax = 0.62;
|
|
|
|
|
+
|
|
|
|
|
+ if (this.chinaLine && this.chinaLine.material) {
|
|
|
this.chinaLine.visible = chinaVis;
|
|
this.chinaLine.visible = chinaVis;
|
|
|
- const mat = this.chinaLine.material;
|
|
|
|
|
- if (mat) {
|
|
|
|
|
- const pulse = 0.55 + 0.45 * Math.sin(now * 0.004);
|
|
|
|
|
- mat.opacity = chinaVis ? 0.20 + 0.35 * pulse : 0.0;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ this.chinaLine.material.opacity = chinaVis ? Math.min(1, mainMax * intensity) : 0.0;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.chinaGlowLine && this.chinaGlowLine.material) {
|
|
|
|
|
+ this.chinaGlowLine.visible = chinaVis;
|
|
|
|
|
+ // 发光层更柔,允许稍微高一点制造“粗亮边缘”
|
|
|
|
|
+ this.chinaGlowLine.material.opacity = chinaVis ? Math.min(1, glowMax * Math.pow(intensity, 0.85)) : 0.0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ----- Marker intensity -----
|
|
// ----- Marker intensity -----
|
|
@@ -417,11 +501,9 @@ export default {
|
|
|
if (this.wire && this.wire.material) {
|
|
if (this.wire && this.wire.material) {
|
|
|
this.wire.material.opacity = 0.12 * (1 - fadeK);
|
|
this.wire.material.opacity = 0.12 * (1 - fadeK);
|
|
|
}
|
|
}
|
|
|
- if (this.chinaLine && this.chinaLine.material) {
|
|
|
|
|
- // china outline opacity already animated; multiply by (1-fadeK)
|
|
|
|
|
- this.chinaLine.material.opacity *= (1 - fadeK);
|
|
|
|
|
|
|
+ if (this.label && this.label.el) {
|
|
|
|
|
+ this.label.el.style.opacity = String(1 - fadeK);
|
|
|
}
|
|
}
|
|
|
- this.label.el.style.opacity = String((Number(this.label.el.style.opacity) || 1) * (1 - fadeK));
|
|
|
|
|
|
|
|
|
|
// ----- China HUD (2D) -----
|
|
// ----- China HUD (2D) -----
|
|
|
// 进入 HUD 阶段:出现 -> 淡出
|
|
// 进入 HUD 阶段:出现 -> 淡出
|
|
@@ -636,7 +718,7 @@ export default {
|
|
|
const mat = new THREE.LineBasicMaterial({
|
|
const mat = new THREE.LineBasicMaterial({
|
|
|
color: 0x66f2ff,
|
|
color: 0x66f2ff,
|
|
|
transparent: true,
|
|
transparent: true,
|
|
|
- opacity: 0.0, // 你时间轴里会动态调
|
|
|
|
|
|
|
+ opacity: 0.95, // 你时间轴里会动态调
|
|
|
blending: THREE.AdditiveBlending,
|
|
blending: THREE.AdditiveBlending,
|
|
|
depthWrite: false
|
|
depthWrite: false
|
|
|
});
|
|
});
|