loadCar(type) {
// 建立車輛新節點 let car = new ht.Node(); // 根據車輛類型建立加載對應車輛模型 switch (type) { case 'familyCar': car.s('shape3d', 'models/HT模型庫/交通/車輛/家用車.json'); break; case 'truck': car.s('shape3d', 'models/HT模型庫/交通/車輛/卡車.json'); break; case 'jeep': car.s('shape3d', 'models/HT模型庫/交通/車輛/吉普車.json'); break; ... default: console.log('NO THIS TYPE CAR!'); break; } // 設置車輛不可選擇和不可移動 car.s({ '3d.selectable': false, '3d.movable': false }); // 設置錨點 --- 車的頭部 car.setAnchor3d(1, 0, 0.5); // 設置初始位置 car.setPosition3d(0, 100000, 0); let typeIndex = 1; // 判斷是否此前生成了這種類型的車輛 this.g3dDm.each(data => { if (data.getTag() === type + typeIndex) { typeIndex++; } }) // 設置車輛節點標籤 car.setTag(type + typeIndex); // 設置車輛節點的名字 car.setDisplayName(type); // 將車輛節點添加到數據模型中 this.g3dDm.add(car); }
而關於管道動畫的實現上,基於 ht.Default.startAnim() 封裝了一個 move 的動畫函數是節點沿着路徑平滑移動的封裝函數,主要參數爲:html
經過繪製一條運行路線的管道,ht.Default.getLineCacheInfo() 獲得這條管道的點位和分割信息 cache,而後管道信息經過 ht.Default.getLineLength() 獲得管道的長度,而且經過 ht.Default.getLineOffset() 來獲取連線或者管道指定比例的偏移信息,從而達到移動的效果,是爲了經過 node.lookAtX() 來獲取節點下一個面對的朝向的位置信息,並設置節點此時的位置,從而達到節點沿着路徑平滑移動的效果。前端
move(node, path, duration = 20000, animParams) {
// path._cache_ 裏面存着管道的節點信息 let cache = path._cache_; // 若是沒有緩存信息,則獲取 path._cache_ 裏面存着管道的節點信息 if (!cache) { cache = path._cache_ = ht.Default.getLineCacheInfo(path.getPoints(), path.getSegments()); } // 獲取管道緩存信息的長度 const len = ht.Default.getLineLength(cache); // 設置動畫對象初始化 animParams = animParams || {}; // 設置 action 爲 animParams 的動畫執行函數 const action = animParams.action; // 動畫執行部分 animParams.action = (v, t) => { // 獲取管道運動的偏移信息 const offset = ht.Default.getLineOffset(cache, len * v); // 獲取偏移位置上的點 const point = offset.point; // 設置節點看向的下一個位置 node.lookAtX([point.x, point.y, point.z], "forward "); // 設置節點的位置 node.p3(point.x, point.y, point.z); // 判斷動畫是否執行完 if (action) action(); }; // 循環調用動畫執行函數 return loop(animParams.action, duration); } // 循環動畫函數 loop(action, duration) { return ht.Default.startAnim({ duration: duration, action: action }); }
在交互實現上,經過點擊選中攝像頭後,使這個攝像頭的錐形區域變爲直線,表示爲選中狀態同時標記選中的攝像頭的選中先後順序,而且經過派發事件驅使 2D 圖紙上顯示攝像頭彈窗,在彈窗顯示的同時,經過計算獲得實時變更的中心點位置信息(center),只要實時經過全局派發事件把位置信息傳輸到攝像頭彈窗場景,就能起到攝像頭場景視角與主場景中所點擊攝像頭的視角同步;取消彈窗顯示的交互方式是經過雙擊場景背景,恢復攝像頭錐形區域而且派發事件去隱藏 2D圖紙上的攝像頭彈窗:node
// 全局事件派發器 var G = {} window.G = G; G.event = new ht.Notifier(); handleInteractive(e) { const {kind, data} = e; if(kind === 'clickData') { // 判斷點擊節點是否帶有標籤,沒有標籤則 return let tag = data.getTag(); if(!tag) return; // 判斷標籤名爲攝像頭 if(tag.indexOf('camera') >= 0) { // 設置指定上一個點擊的攝像頭和當前點擊的攝像頭 this.lastClickCamera = this.nowClickCamera; this.nowClickCamera = data; // 若是以前有點擊攝像頭,則初始化攝像頭錐體的大小 if (this.lastClickCamera !== null) { let clickRangeNode = this.lastClickCamera.getChildren()._as[0]; clickRangeNode.s3(300, 150, 500); } // 若是有點擊攝像頭,則設定所點擊攝像頭錐體的大小 if (this.nowClickCamera !== null) { let clickRangeNode = this.nowClickCamera.getChildren()._as[0]; clickRangeNode.s3(5, 5, 500); } // 獲取點擊攝像頭的位置信息 var cameraP3 = nowClickCamera.p3(); // 獲取點擊攝像頭的旋轉信息 var cameraR3 = nowClickCamera.r3(); // 獲取點擊攝像頭的大小信息 var cameraS3 = nowClickCamera.s3(); // 當前錐體起始位置 var realP3 = [cameraP3[0], cameraP3[1] + cameraS3[1] / 2, cameraP3[2] + cameraS3[2] / 2]; // 將當前眼睛位置繞着攝像頭起始位置旋轉獲得正確眼睛位置 var realEye = getCenter(cameraP3, realP3, cameraR3); // 全局事件派發至攝像頭場景改變視角的眼睛 eye 和中心點 center G.event.fire({ type: 'videoCreated', eye: realEye, center: getCenter(realEye, [realEye[0], realEye[1] ,realEye[2] + 5], cameraR3) }); // 視頻彈窗顯示派發 event.fire(SHOW_VIDEO, {g3dDm: this.g3dDm, cameraName:tag}); } } // 雙擊背景隱藏攝像頭場景窗口,並初始化攝像頭錐體的大小 if(kind === 'doubleClickBackground') { // 視頻彈窗隱藏派發 event.fire(HIDE_VIDEO); // 若是以前有點擊攝像頭,則初始化攝像頭錐體的大小 if (this.nowClickCamera !== null) { let clickRangeNode = this.nowClickCamera.getChildren()._as[0]; clickRangeNode.s3(300, 150, 500) } // 設置當前點擊攝像頭爲空 this.nowClickCamera = null; } }
以上所涉及到方法 getCenter(),其實是經過去獲取每一個攝像頭節點在場景中對應的旋轉角度,簡化理解就是一個點 A 圍繞着另一個點 B 旋轉,即中心點位置(center)圍繞着眼睛位置(eye)旋轉,而咱們則須要去計算點 A 的位置(中心點位置 center),這裏經過封裝一個 getCenter 方法用於獲取 3d 場景中點 A 繞着點 B 旋轉 angle 角度以後獲得的點 A 在 3d 場景中的位置,方法中採用了 HT 封裝的 ht.Math 下面的方法,如下爲實現的代碼:ios
實現代碼以下:ajax
// pointA 爲 pointB 圍繞的旋轉點 // pointB 爲須要旋轉的點 // r3 爲旋轉的角度數組 [xAngle, yAngle, zAngle] 爲繞着 x, y, z 軸分別旋轉的角度 const getCenter = function(pointA, pointB, r3) { const mtrx = new ht.Math.Matrix4(); const euler = new ht.Math.Euler(); const v1 = new ht.Math.Vector3(); const v2 = new ht.Math.Vector3(); mtrx.makeRotationFromEuler(euler.set(r3[0], r3[1], r3[2])); v1.fromArray(pointB).sub(v2.fromArray(pointA)); v2.copy(v1).applyMatrix4(mtrx); v2.sub(v1); return [pointB[0] + v2.x, pointB[1] + v2.y, pointB[2] + v2.z]; };
2.2 實景攝像頭的實現原理算法
例如經過一個簡單的 RTMP 視頻流的對接就能夠明白其實現的原理。對於的視頻的載入,須要用到 video.js 的插件進行展現,因此先引入插件,而後對接視頻流後,也是一樣經過全局事件派發到 HT 的渲染元素 renderHTML 將視頻流渲染到場景圖紙中,如下是實現的僞代碼:json
// 引入 video.js 插件 <script src="./js/video.js"></script> // 經過全局事件派發到渲染元素 renderHTML 去渲染視頻到場景圖紙中 G.event.add(function(e){ if(e.type==='videoCreated'){ var div=e.div; div.innerHTML='<video id="video" class="video-js vjs-default-skin"><source src="rtmp://10.10.70.57/live/test" type="rtmp/flv"></video>'; window.player = videojs('video'); } });
ajax 和 axios 要實時獲取接口數據得經過輪詢調用接口的形式進行傳輸,而 WebSocket 能夠雙向進行數據傳輸,在選擇運用上能夠匹配本身的實現需求。本系統是採用經過 axios 調用接口獲取實時數據。axios
示例中的柱狀圖和折線圖,是經過 HT 裏的機制下去使用 eEcharts 上一些圖表進行自定義配置而實現的,繼而經過對 axios 接口輪詢調用載入數據,展示了實時的路口監控數據信息:數組
loadData() {
// 獲取圖紙的數據模型 let dm = this.g2d.dm(); // 獲取車流量接口的數據 axios.get('/traffic').then(res => { // 接入日車流量折線圖的數據 this.lineChart1.a({ 'seriesData1': res.lineChartData1, 'axisData' : res.axisData }); // 接入車輛運行高峯折線圖的數據 this.lineChart2.a({ 'seriesData1': res.lineChartData2, 'axisData' : res.axisData }) // 採用數字跳動的方式載入一些數據內容 setBindingDatasWithAnim(dm, res, 800, v => Math.round(v)); // 接入運行峯值的時刻 this.peakTime.s('text', res.peakTime); }); // 載入設備運行狀態的數據 axios.get('/equipmentStatus').then(res => { setBindingDatasWithAnim(dm, res, 800, v => Math.round(v)); }); // 載入事故統計的數據 axios.get('/accident').then(res => { setBindingDatasWithAnim(dm, res, 800, v => Math.round(v)); // 接入每個月事故柱狀圖的數據 this.accidentBar.a({ axisData: res.axisData, seriesData1: res.seriesData1 }) }); }
addTableRow() {
// 獲取表格節點 let table = this.table; // 經過 axios 的 promise 請求接口數據 axios.get('getEvent').then(res => { // 獲取表格節點滾動信息的數據綁定 let tableData = table.a('dataSource'); // 經過向 unshift() 方法可向滾動信息數組的開頭添加一個或更多元素 tableData.unshift(res); // 初始化表格的縱向偏移 table.a('ty', -54); // 開啓表格滾動動畫 ht.Default.startAnim({ duration: 600, // 動畫執行函數 action action: (v, t) => { table.a({ // 經過添加數據後,橫向滾動 100 'firstRowTx': 100 * (1 - v), // 第一行行高出現的透明度漸變效果 'firstRowOpacity': v, // 縱向偏移 54 的高度 'ty': (v - 1) * 54 }); } }); }); }