From 3bbfedeefa0aa2e5fd1487b3e021a7c2b96fd1e4 Mon Sep 17 00:00:00 2001 From: fukunee Date: Sat, 19 Sep 2026 14:23:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9C=B0=E8=A1=A8=E9=99=84=E8=BF=91=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=8F=B3=E9=94=AE=E7=8E=AF=E8=A7=86=EF=BC=88=E4=BB=A5?= =?UTF-8?q?=E7=82=B9=E5=87=BB=E7=82=B9=E4=B8=BA=E7=90=83=E5=BF=83=EF=BC=8C?= =?UTF-8?q?=E6=9D=BE=E5=BC=80=E5=BD=92=E4=BD=8D=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/scene/CesiumGlobe.js | 97 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/src/scene/CesiumGlobe.js b/src/scene/CesiumGlobe.js index c3c2523..8ea672b 100644 --- a/src/scene/CesiumGlobe.js +++ b/src/scene/CesiumGlobe.js @@ -114,6 +114,10 @@ function makeStickyNote(text, fontSize, selected = false) { return { canvas: c, scale: 1 / S, width: cw, height: ch, tipX: B, tipY: B + h / 2 }; } +// 右键“环视”手势:放大到地表附近时,右键拖动以点击点为球心环视,松开归位 +const LOOK_GROUND_MAX_HEIGHT = 1e6; // 相机高度低于 1000 km 才启用 +const LOOK_RATE = 0.005; // 每像素旋转弧度 + export class CesiumGlobe { constructor({ onPick } = {}) { this.onPick = onPick; @@ -250,6 +254,7 @@ export class CesiumGlobe { this._buildMarkers(); this._setupPick(); + this._setupLookAround(); this._loadBoundaries(); this._buildCityLabels(); @@ -350,6 +355,98 @@ export class CesiumGlobe { viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK); } + // 右键环视:地表附近右键拖动,以点击点为球心调整视角,松开归位 + _setupLookAround() { + const viewer = this.viewer; + const scene = viewer.scene; + const camera = viewer.camera; + this._lookState = null; + this._orbitScratch = { + offset: new Cesium.Cartesian3(), + up: new Cesium.Cartesian3(), + right: new Cesium.Cartesian3(), + qAz: new Cesium.Quaternion(), + qEl: new Cesium.Quaternion(), + q: new Cesium.Quaternion(), + rotMat: new Cesium.Matrix3(), + newOffset: new Cesium.Cartesian3(), + newPos: new Cesium.Cartesian3(), + dir: new Cesium.Cartesian3(), + upProj: new Cesium.Cartesian3(), + }; + + const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); + + handler.setInputAction((movement) => { + if (camera.positionCartographic.height > LOOK_GROUND_MAX_HEIGHT) return; + const position = + scene.pickPosition(movement.position) || + camera.pickEllipsoid(movement.position, scene.globe.ellipsoid); + if (!Cesium.defined(position)) return; + this._lookState = { + pivot: Cesium.Cartesian3.clone(position), + position: Cesium.Cartesian3.clone(camera.position), + direction: Cesium.Cartesian3.clone(camera.direction), + up: Cesium.Cartesian3.clone(camera.up), + lastX: movement.position.x, + lastY: movement.position.y, + }; + }, Cesium.ScreenSpaceEventType.RIGHT_DOWN); + + handler.setInputAction((movement) => { + if (!this._lookState) return; + const s = this._lookState; + const dx = movement.position.x - s.lastX; + const dy = movement.position.y - s.lastY; + s.lastX = movement.position.x; + s.lastY = movement.position.y; + this._orbitAroundPivot(s.pivot, dx * LOOK_RATE, dy * LOOK_RATE); + }, Cesium.ScreenSpaceEventType.RIGHT_DRAG); + + handler.setInputAction(() => { + if (!this._lookState) return; + const s = this._lookState; + camera.setView({ + destination: s.position, + orientation: { direction: s.direction, up: s.up }, + }); + this._lookState = null; + }, Cesium.ScreenSpaceEventType.RIGHT_UP); + + this._lookHandler = handler; + } + + // 围绕 pivot 旋转相机(水平=方位角,垂直=俯仰角) + _orbitAroundPivot(pivot, dAzimuth, dElevation) { + const camera = this.viewer.camera; + const s = this._orbitScratch; + const offset = Cesium.Cartesian3.subtract(camera.position, pivot, s.offset); + const up = Cesium.Cartesian3.normalize(pivot, s.up); // 球心在原点,pivot 方向即法线 + const right = Cesium.Cartesian3.normalize( + Cesium.Cartesian3.cross(camera.direction, camera.up, s.right), + s.right, + ); + Cesium.Quaternion.fromAxisAngle(up, -dAzimuth, s.qAz); + Cesium.Quaternion.fromAxisAngle(right, -dElevation, s.qEl); + Cesium.Quaternion.multiply(s.qEl, s.qAz, s.q); + Cesium.Matrix3.fromQuaternion(s.q, s.rotMat); + Cesium.Matrix3.multiplyByVector(s.rotMat, offset, s.newOffset); + Cesium.Cartesian3.add(pivot, s.newOffset, s.newPos); + const dir = Cesium.Cartesian3.normalize( + Cesium.Cartesian3.subtract(pivot, s.newPos, s.dir), + s.dir, + ); + const zDotDir = dir.z; + s.upProj.x = -dir.x * zDotDir; + s.upProj.y = -dir.y * zDotDir; + s.upProj.z = 1 - zDotDir * zDotDir; + Cesium.Cartesian3.normalize(s.upProj, s.upProj); + camera.setView({ + destination: s.newPos, + orientation: { direction: dir, up: s.upProj }, + }); + } + // 相机平滑飞至某个标记上空并居中:回到与初始一致的高度与北朝上方向,左键拖拽手感不变 flyToEntry(index) { const entry = ENTRIES[index];