在浏览室内场景,视角飞行等场景中,除了第三人称的视角(相机视角)以外,还有一种视角就是第一人称视角,也就是室内模型漫游,或者以某个模型为视角切换基准,带来沉浸式体验。常用于模型室内漫游,景点漫游,管线漫游,河道漫游等等,关键点在于手动切换相机三元素。
具体实现如下:
1,初始化相机,设定相机位置(样本点)
2,控制相机运动
3,设置飞行的时间
4,相机原地定点转向
5,计算两点间的偏航角(弧度和角度转换)
具体如下
<!DOCTYPE html><html lang="en"><head> <!-- Use correct character set. --> <meta charset="utf-8"/> <!-- Tell IE to use the latest, best version. --> <meta http-equiv="X-UA-Compatible" content="IE=edge"/> <!-- Make the application on mobile take up the full browser screen and disable user scaling. --> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"/> <title>模型室内漫游</title> <script src="https://data.sunbt.ltd/lib/Cesium-1.89/Build/Cesium/Cesium.js"></script> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <style> @import url(https://data.sunbt.ltd/lib/Cesium-1.89/Build/Cesium/Widgets/widgets.css); html, body, #temp { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } #cesiumContainer { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; } </style></head><body><div id="temp"> <div style="display: -webkit-flex;display: flex;width: 100%;height: 100%"> <div style="width: 90%;height: 100%"> <div id="cesiumContainer"></div> </div> <div style="width: 10%;height: 100%;background-color: #d3d3d3;padding: 30px"> <button class="btn" @click="modelLocate">模型定位</button> <button class="btn" @click="roamIndoor">模型漫游</button> <button class="btn" @click="cancelFlyEvent">停止漫游</button> </div> </div></div><script> let EarthComp = new Vue({ el: "#temp", data: { _earth: undefined, // 注意:Earth和Cesium的相关变量放在vue中,必须使用下划线作为前缀! _viewer: undefined, model: null,//切片模型 marks: [], marksIndex: 1, pitchValue: -10, remainTime: 0, usedTime: 0, }, mounted: function () { let that = this; this.earthInit(); }, methods: { /** * 地球初始化 */ earthInit() { //初始页面加载 //Cesium.Ion.defaultAccessToken = cesium_tk; let viewer = new Cesium.Viewer('cesiumContainer', { geocoder: false, // 位置查找工具 baseLayerPicker: false,// 图层选择器(地形影像服务) timeline: false, // 底部时间线 homeButton: false,// 视角返回初始位置 fullscreenButton: false, // 全屏 animation: false, // 左下角仪表盘(动画器件) sceneModePicker: false,// 选择视角的模式(球体、平铺、斜视平铺) navigationHelpButton: false, //导航帮助按钮 imageryProvider: new Cesium.UrlTemplateImageryProvider({ url:'https://tiles.geovis.online/base/v1/img/{z}/{x}/{y}?format=webp&token=c12979f9caccfc2e24b822852976c264c3c33403a87bafd21b0f4eb2de4bd79e', maximumLevel: 18, }) }); // //调用影响中文注记服务 // viewer.imageryLayers.addImageryProvider(new Cesium.WebMapTileServiceImageryProvider({ // url: TDT_CIA_C, // layer: "tdtImg_c", // style: "default", // format: "tiles", // tileMatrixSetID: "c", // subdomains: ["t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7"], // tilingScheme: new Cesium.GeographicTilingScheme(), // tileMatrixLabels: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19"], // maximumLevel: 50, // show: false // })) this._viewer = viewer; // 去除版权信息 this._viewer._cesiumWidget._creditContainer.style.display = "none"; // 初始化模型位置 this.addModel(); }, /** * 添加模型 */ addModel() { // 3Dtiles切片地址 let tileset = this._viewer.scene.primitives.add(new Cesium.Cesium3DTileset({ url: 'http://192.168.1.197:8080/data/3dtiles/tileset.json', modelMatrix: Cesium.Matrix4.fromArray( [0.9972458032561666, 0.04372029028528979, 0.05991113506964879, 0, -0.03623787897545647, 0.9920229449104262, -0.12073646051879428, 0, -0.06471185374661931, 0.11823287609043515, 0.9908750491338749, 0, -663.0794944260269, 1211.490494620055, 2974.1003134818748, 1]), })); this.model = tileset; this._viewer.flyTo(tileset, { offset: { heading: Cesium.Math.toRadians(20.0),//方向 pitch: Cesium.Math.toRadians(-25),//倾斜角度 range: 1000 } }); }, /** * 模型定位 */ modelLocate() { let that = this; this._viewer.flyTo(this.model, { offset: { heading: Cesium.Math.toRadians(120.0),//方向 pitch: Cesium.Math.toRadians(-10),//倾斜角度 range: 450 } }); }, /** * 室内漫游 * 初始化相机位置 */ roamIndoor() { let that = this; /** 相机视角飞行 开始 **/ this.marks = [ // height:相机高度(单位米) flytime:相机两个标注点飞行时间(单位秒) {lng: 118.69184532414744, lat: 32.16119279415843, height: 5, flytime: 5}, {lng: 118.69192079776337, lat: 32.16112689859642, height: 5, flytime: 5}, {lng: 118.69223881961578, lat: 32.16098664838045, height: 5, flytime: 5} ];// 地标集合 根据地标顺序来进行漫游 this.marksIndex = 1; this.pitchValue = -10; this._viewer.scene.camera.flyTo({ //定位坐标点,建议使用谷歌地球坐标位置无偏差 destination: Cesium.Cartesian3.fromDegrees(that.marks[0].lng, that.marks[0].lat, that.marks[0].height), duration: 2, //定位的时间间隔 orientation: { heading: Cesium.Math.toRadians(120.0),//方向 pitch: Cesium.Math.toRadians(-10),//倾斜角度 roll: 0 } }); setTimeout(function () { that.flyExtent(); }, 2000); }, //控制相机运动方法(范围) flyExtent() { let that = this; let marks = that.marks; // let marksIndex = that.marksIndex; let pitchValue = that.pitchValue; let viewer = that._viewer; // 相机看点的角度,如果大于0那么则是从地底往上看,所以要为负值 let pitch = Cesium.Math.toRadians(pitchValue); // 时间间隔2秒钟 that.setExtentTime(that.marks[that.marksIndex].flytime); let Exection = function TimeExecution() { let preIndex = that.marksIndex - 1; //当到达最后一个点时,继续漫游 if (that.marksIndex === 0) { preIndex = marks.length - 1; } //计算偏航角 let heading = that.bearing(marks[preIndex].lat, marks[preIndex].lng, marks[that.marksIndex].lat, marks[that.marksIndex].lng); heading = Cesium.Math.toRadians(heading); // 当前已经过去的时间,单位s let delTime = Cesium.JulianDate.secondsDifference(viewer.clock.currentTime, viewer.clock.startTime); let originLat = that.marksIndex === 0 ? marks[marks.length - 1].lat : marks[that.marksIndex - 1].lat; let originLng = that.marksIndex === 0 ? marks[marks.length - 1].lng : marks[that.marksIndex - 1].lng; //计算相机下一次的位置 let endPosition = Cesium.Cartesian3.fromDegrees( (originLng + (marks[that.marksIndex].lng - originLng) / marks[that.marksIndex].flytime * delTime), (originLat + (marks[that.marksIndex].lat - originLat) / marks[that.marksIndex].flytime * delTime), marks[that.marksIndex].height ); viewer.scene.camera.setView({ destination: endPosition, orientation: { heading: heading, pitch: pitch, } }); //当到达下一个点的时候,重新设置相机偏航角 if (Cesium.JulianDate.compare(viewer.clock.currentTime, viewer.clock.stopTime) >= 0) { viewer.clock.onTick.removeEventListener(Exection); that.changeCameraHeading(); } }; //让cesium的时钟方法来监听该方法 viewer.clock.onTick.addEventListener(Exection); }, // 设置飞行的时间到viewer的时钟里 setExtentTime(time) { let that = this; let viewer = that._viewer; let startTime = Cesium.JulianDate.fromDate(new Date()); let stopTime = Cesium.JulianDate.addSeconds(startTime, time, new Cesium.JulianDate()); viewer.clock.startTime = startTime.clone(); // 开始时间 viewer.clock.stopTime = stopTime.clone(); // 结速时间 viewer.clock.currentTime = startTime.clone(); // 当前时间 viewer.clock.clockRange = Cesium.ClockRange.CLAMPED; // 行为方式 viewer.clock.clockStep = Cesium.ClockStep.SYSTEM_CLOCK; // 时钟设置为当前系统时间; 忽略所有其他设置。 }, // 相机原地定点转向 changeCameraHeading() { let that = this; let marks = that.marks; let marksIndex = that.marksIndex; let pitchValue = that.pitchValue; let viewer = that._viewer; let nextIndex = that.marksIndex + 1; if (that.marksIndex === marks.length - 1) { nextIndex = 0; } // 计算两点之间的方向 let heading = that.bearing(marks[marksIndex].lat, marks[marksIndex].lng, marks[nextIndex].lat, marks[nextIndex].lng); // 相机看点的角度,如果大于0那么则是从地底往上看,所以要为负值 let pitch = Cesium.Math.toRadians(pitchValue); // 给定飞行一周所需时间,比如10s, 那么每秒转动度数 let angle = (heading - Cesium.Math.toDegrees(viewer.camera.heading)) / 2; if (angle < -90) angle += 180; else if (angle > 90) angle -= 180; // 时间间隔2秒钟 that.setExtentTime(2); // 相机的当前heading let initialHeading = viewer.camera.heading; let exection = function TimeExecution() { // 当前已经过去的时间,单位s let delTime = Cesium.JulianDate.secondsDifference(viewer.clock.currentTime, viewer.clock.startTime); let heading = Cesium.Math.toRadians(delTime * angle) + initialHeading; viewer.scene.camera.setView({ orientation: { heading: heading, pitch: pitch, } }); if (Cesium.JulianDate.compare(viewer.clock.currentTime, viewer.clock.stopTime) >= 0) { viewer.clock.onTick.removeEventListener(exection); that.marksIndex = ++that.marksIndex >= marks.length ? 0 : that.marksIndex; that.flyExtent(); } }; //让cesium的时钟方法来监听该方法 viewer.clock.onTick.addEventListener(exection); }, //计算两点间的偏航角 bearing(startLat, startLng, destLat, destLng) { startLat = this.toRadians(startLat); startLng = this.toRadians(startLng); destLat = this.toRadians(destLat); destLng = this.toRadians(destLng); let y = Math.sin(destLng - startLng) * Math.cos(destLat); let x = Math.cos(startLat) * Math.sin(destLat) - Math.sin(startLat) * Math.cos(destLat) * Math.cos(destLng - startLng); let brng = Math.atan2(y, x); let brngDgr = this.toDegrees(brng); return (brngDgr + 360) % 360; }, /** 飞行时 camera的方向调整(heading) 开始 **/ // Converts from degrees to radians. toRadians(degrees) { return degrees * Math.PI / 180; }, // Converts from radians to degrees. toDegrees(radians) { return radians * 180 / Math.PI; }, /** 停止漫游方法 **/ cancelFlyEvent(eventType) { let that = this; //获取被clock监听的全部事件数量 let len = that._viewer.clock.onTick.numberOfListeners; for (let i = 0; i < len; i++) { //将被监听的方法移除来停止方法 that._viewer.clock.onTick.removeEventListener(that._viewer.clock.onTick._listeners[i]); } }, }, })</script></body></html>