From 66fac3771d53d4f28235d1fc4609a2d014d0dba5 Mon Sep 17 00:00:00 2001 From: fukunee Date: Sat, 19 Sep 2026 14:45:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=B3=E9=94=AE=E7=8E=AF=E8=A7=86=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E6=89=8B=E5=8A=A8=E7=BB=95=E7=82=B9=E5=87=BB=E7=82=B9?= =?UTF-8?q?=E6=97=8B=E8=BD=AC=EF=BC=8C=E7=B2=BE=E7=A1=AE=E9=99=90=E5=88=B6?= =?UTF-8?q?=20=C2=B140=C2=B0/=C2=B120=C2=B0=EF=BC=8C=E6=9D=BE=E5=BC=80?= =?UTF-8?q?=E5=BD=92=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/scene/CesiumGlobe.js | 133 ++++++++++++++++++++++++++------------- 1 file changed, 91 insertions(+), 42 deletions(-) diff --git a/src/scene/CesiumGlobe.js b/src/scene/CesiumGlobe.js index 4e2347d..3ec2a1c 100644 --- a/src/scene/CesiumGlobe.js +++ b/src/scene/CesiumGlobe.js @@ -26,6 +26,11 @@ const IMAGERY_URL = const TERRAIN_URL = 'https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer'; +// 右键环视角度限制与灵敏度 +const MAX_LOOK_ROT = (40 * Math.PI) / 180; // 左右旋转 ±40° +const MAX_LOOK_TILT = (20 * Math.PI) / 180; // 上下倾角 ±20° +const LOOK_RATE = Math.PI / 720; // 每像素约 0.25° + // 计算多边形集合的球面面积(km²),用于按国土面积分级国家名字号 function sphericalAreaKm2(polys) { let area = 0; @@ -212,7 +217,6 @@ export class CesiumGlobe { Cesium.CameraEventType.PINCH, ]; controller.tiltEventTypes = [ - Cesium.CameraEventType.RIGHT_DRAG, // 传统右键:以点击处为球心倾斜视角 Cesium.CameraEventType.PINCH, { eventType: Cesium.CameraEventType.LEFT_DRAG, @@ -226,7 +230,7 @@ export class CesiumGlobe { // 精细化渲染:质量与性能的平衡 scene.globe.maximumScreenSpaceError = 2.0; // 默认细分,最省性能 scene.globe.tileCacheSize = 600; // 更大的瓦片缓存,减少细节刷新 - scene.fxaa = false; // 关闭抗锯齿后处理,省一个全屏通道 + scene.fxaa = true; // 开启抗锯齿 viewer.resolutionScale = 1.0; // 关闭超采样(DPR 已提供足够分辨率),省填充率 scene.maximumRenderTimeChange = Infinity; // 配合 requestRenderMode:静止时停止重绘,大幅省电省帧 @@ -255,7 +259,7 @@ export class CesiumGlobe { this._buildMarkers(); this._setupPick(); - this._setupRightRestore(); + this._setupLookAround(); this._loadBoundaries(); this._buildCityLabels(); @@ -356,65 +360,110 @@ export class CesiumGlobe { viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK); } - // 右键操作(原生 tilt)松开后自动归位 + 拖动角度限制(左右旋转 ±40°,上下倾角 ±20°) - _setupRightRestore() { + // 右键环视:以点击处为球心,左右旋转 ±40°、上下倾角 ±20°,松开自动归位。 + // 不用原生 tilt(RIGHT_DRAG):原生 tilt 会绕点击点做轨道移动,heading/pitch + // 是相对“不断移动的相机位置”计算的,无法干净地限制成 40°/20°。 + _setupLookAround() { const scene = this.viewer.scene; const camera = this.viewer.camera; const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas); - this._rightRestore = null; + this._lookState = null; + this._orbitScratch = { + offset: new Cesium.Cartesian3(), + upAxis: new Cesium.Cartesian3(), + eastAxis: 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(), + }; - handler.setInputAction(() => { - this._rightRestore = { + handler.setInputAction((movement) => { + const pivot = + scene.pickPosition(movement.position) || + camera.pickEllipsoid(movement.position, 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); + } + this._lookState = { + pivot: Cesium.Cartesian3.clone(pivot), + startOffset: Cesium.Cartesian3.clone(offset), + upAxis: Cesium.Cartesian3.clone(upAxis), + eastAxis: Cesium.Cartesian3.clone(eastAxis), position: Cesium.Cartesian3.clone(camera.position), direction: Cesium.Cartesian3.clone(camera.direction), up: Cesium.Cartesian3.clone(camera.up), - heading: camera.heading, - pitch: camera.pitch, + azimuth: 0, + elevation: 0, + 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.endPosition.x - s.lastX; + const dy = movement.endPosition.y - s.lastY; + s.lastX = movement.endPosition.x; + 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._orbitCamera(s); + }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); + handler.setInputAction(() => { - if (!this._rightRestore) return; - const s = this._rightRestore; - this._rightRestore = null; + if (!this._lookState) return; + const s = this._lookState; + this._lookState = null; camera.setView({ destination: s.position, orientation: { direction: s.direction, up: s.up }, }); }, Cesium.ScreenSpaceEventType.RIGHT_UP); - this._rightRestoreHandler = handler; - this.viewer.scene.postUpdate.addEventListener(() => this._clampLookAngles()); + this._lookHandler = handler; } - // 右键拖拽期间,限制相对起始朝向的旋转/倾角 - _clampLookAngles() { - if (!this._rightRestore) return; + // 按累计角度(已限制)把相机绕点击点旋转;始终保持北朝上、看向点击点 + _orbitCamera(s) { const camera = this.viewer.camera; - const s = this._rightRestore; - const MAX_ROT = (40 * Math.PI) / 180; - const MAX_TILT = (20 * Math.PI) / 180; - - let heading = camera.heading; - let dh = heading - s.heading; - while (dh > Math.PI) dh -= 2 * Math.PI; - while (dh < -Math.PI) dh += 2 * Math.PI; - const rot = Math.max(-MAX_ROT, Math.min(MAX_ROT, dh)); - - const pitch = camera.pitch; - const dp = pitch - s.pitch; - const tilt = Math.max(-MAX_TILT, Math.min(MAX_TILT, dp)); - - if (rot !== dh || tilt !== dp) { - camera.setView({ - destination: camera.position, - orientation: { - heading: s.heading + rot, - pitch: s.pitch + tilt, - roll: camera.roll, - }, - }); - } + const sc = this._orbitScratch; + Cesium.Quaternion.fromAxisAngle(s.upAxis, 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, + ); + const z = dir.z; + sc.upProj.x = -dir.x * z; + sc.upProj.y = -dir.y * z; + sc.upProj.z = 1 - z * z; + Cesium.Cartesian3.normalize(sc.upProj, sc.upProj); + camera.setView({ + destination: Cesium.Cartesian3.clone(sc.newPos), + orientation: { + direction: Cesium.Cartesian3.clone(dir), + up: Cesium.Cartesian3.clone(sc.upProj), + }, + }); } // 相机平滑飞至某个标记上空并居中:回到与初始一致的高度与北朝上方向,左键拖拽手感不变