diff --git a/src/scene/CesiumGlobe.js b/src/scene/CesiumGlobe.js index a3a00f9..cbb9bc3 100644 --- a/src/scene/CesiumGlobe.js +++ b/src/scene/CesiumGlobe.js @@ -364,17 +364,64 @@ export class CesiumGlobe { viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK); } - // 右键环视:相机“原地倾倒”(位置不动,只改视线方向)。左右 = 经度方向、上下 = 纬度方向,强制北朝上。 - // 关键:直接算 heading/pitch 传给 setView,绕过 setView 在近天底处用 up 反推 heading 的奇点—— - // 否则初期的小角度倾斜会被翻转(表现为“向北平移”)。 + // 右键环视:绕相机注视点(地形表面)做轨道倾倒。左右 = 经度方向、上下 = 纬度方向,强制北朝上。 + // 关键:把轨道后的视线方向转到 ENU 后直接求 heading/pitch 传给 setView, + // 绕过 setView 在近天底处用 up 反推 heading 的奇点(否则小角度倾斜会被翻转)。 _setupLookAround() { const scene = this.viewer.scene; const camera = this.viewer.camera; const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); this._lookState = null; + this._orbitScratch = { + offset: new Cesium.Cartesian3(), + upAxis: new Cesium.Cartesian3(), + eastAxis: new Cesium.Cartesian3(), + northAxis: 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(), + enu: new Cesium.Matrix4(), + invEnu: new Cesium.Matrix4(), + localDir: new Cesium.Cartesian3(), + }; handler.setInputAction((movement) => { + // 旋转中心 = 相机当前注视的屏幕中心点(地形表面),而不是点击点 + const canvas = scene.canvas; + const center = Cesium.Cartesian2.fromElements( + canvas.clientWidth / 2, + canvas.clientHeight / 2, + ); + const ray = camera.getPickRay(center); + let pivot = scene.globe.pick(ray, scene); + if (!Cesium.defined(pivot)) { + pivot = camera.pickEllipsoid(center, scene.globe.ellipsoid); + } + if (!Cesium.defined(pivot)) return; + const sc = this._orbitScratch; + const offset = Cesium.Cartesian3.subtract(camera.position, pivot, sc.offset); + const upAxis = Cesium.Cartesian3.normalize(pivot, sc.upAxis); + const eastAxis = Cesium.Cartesian3.cross(Cesium.Cartesian3.UNIT_Z, pivot, sc.eastAxis); // 上下拖动(纬度方向) + if (Cesium.Cartesian3.magnitudeSquared(eastAxis) < 1e-12) { + eastAxis.x = 1; + eastAxis.y = 0; + eastAxis.z = 0; // 极点退化兜底 + } else { + Cesium.Cartesian3.normalize(eastAxis, eastAxis); + } + const northAxis = Cesium.Cartesian3.normalize( + Cesium.Cartesian3.cross(upAxis, eastAxis, sc.northAxis), // 左右拖动(经度方向) + sc.northAxis, + ); this._lookState = { + pivot: Cesium.Cartesian3.clone(pivot), + startOffset: Cesium.Cartesian3.clone(offset), + eastAxis: Cesium.Cartesian3.clone(eastAxis), + northAxis: Cesium.Cartesian3.clone(northAxis), position: Cesium.Cartesian3.clone(camera.position), direction: Cesium.Cartesian3.clone(camera.direction), up: Cesium.Cartesian3.clone(camera.up), @@ -394,7 +441,7 @@ export class CesiumGlobe { s.lastY = movement.endPosition.y; s.azimuth = Cesium.Math.clamp(s.azimuth + dx * LOOK_RATE, -MAX_LOOK_ROT, MAX_LOOK_ROT); s.elevation = Cesium.Math.clamp(s.elevation + dy * LOOK_RATE, -MAX_LOOK_TILT, MAX_LOOK_TILT); - this._tiltCamera(s); + this._orbitCamera(s); }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); handler.setInputAction(() => { @@ -410,14 +457,32 @@ export class CesiumGlobe { this._lookHandler = handler; } - // 原地倾倒:由累计的经度角(azimuth)/纬度角(elevation)直接求 heading/pitch,roll 固定 0(北朝上) - _tiltCamera(s) { - const el = s.elevation; - const az = s.azimuth; - const heading = Math.atan2(-Math.cos(el) * Math.sin(az), Math.sin(el)); - const pitch = Math.asin(-Math.cos(el) * Math.cos(az)); - this.viewer.camera.setView({ - destination: s.position, + // 轨道倾倒:左右绕南北轴、上下绕东西轴,位置沿轨道移动且始终看向注视点;直接算 heading/pitch 传 setView + _orbitCamera(s) { + const camera = this.viewer.camera; + const sc = this._orbitScratch; + Cesium.Quaternion.fromAxisAngle(s.northAxis, s.azimuth, sc.qAz); + Cesium.Quaternion.fromAxisAngle(s.eastAxis, s.elevation, sc.qEl); + Cesium.Quaternion.multiply(sc.qEl, sc.qAz, sc.q); + Cesium.Matrix3.fromQuaternion(sc.q, sc.rotMat); + Cesium.Matrix3.multiplyByVector(sc.rotMat, s.startOffset, sc.newOffset); + Cesium.Cartesian3.add(s.pivot, sc.newOffset, sc.newPos); + const dir = Cesium.Cartesian3.normalize( + Cesium.Cartesian3.subtract(s.pivot, sc.newPos, sc.dir), + sc.dir, + ); + // 视线方向转到 newPos 处的 ENU,直接求 heading/pitch(避开 setView 近天底用 up 反推 heading 的翻转) + Cesium.Transforms.eastNorthUpToFixedFrame( + sc.newPos, + this.viewer.scene.globe.ellipsoid, + sc.enu, + ); + Cesium.Matrix4.inverseTransformation(sc.enu, sc.invEnu); + Cesium.Matrix4.multiplyByPointAsVector(sc.invEnu, dir, sc.localDir); + const heading = Math.atan2(sc.localDir.x, sc.localDir.y); + const pitch = Math.asin(sc.localDir.z); + camera.setView({ + destination: Cesium.Cartesian3.clone(sc.newPos), orientation: { heading, pitch, roll: 0 }, }); }