基於 Web 端 3D 地鐵站可視化系統
- 2019 年 10 月 21 日
- 筆記
前言
工業互聯網,物聯網,可視化等名詞在我們現在信息化的大背景下已經是耳熟能詳,日常生活的交通,出行,吃穿等可能都可以用信息化的方式來為我們表達,在傳統的可視化監控領域,一般都是基於 Web SCADA 的前端技術來實現 2D 可視化監控,本系統採用 Hightopo 的 HT for Web 產品來構造輕量化的 3D 可視化場景,該 3D 場景從正面展示了一個地鐵站的現實場景,包括地鐵的實時運行情況,地鐵上下行情況,視頻監控,煙霧報警,電梯運行情況等等,幫助我們直觀的了解當前的地鐵站。
系統中為了幫助用戶更直觀友好的瀏覽當前地鐵站,提供了三種交互模式:
- 第一人稱模式 — 操作就類似行人或車在行進的效果,可以通過鍵盤鼠標控制前進後退。
- 自動巡檢模式 — 該模式下用戶不需要任何操作,場景自動前進後退來巡查當前地鐵站的場景。
- 鼠標操作模式 — 左鍵旋轉場景,右鍵平移場景。
本篇文章通過對地鐵站可視化場景的搭建,動畫代碼的實現,交互模式的原理解析,以及主要功能點的實現進行闡述,幫助我們了解如何使用 HT 實現一個簡單的地鐵站可視化。
預覽地址:基於 HTML5 WebGL 的地鐵站 3D 可視化系統 http://www.hightopo.com/demo/ht-subway/
界面簡介及效果預覽
地鐵運行效果
地鐵從站外開到站內的效果為透明度逐漸增加,速度逐漸降低。
漫遊效果
上述為自動巡檢的漫遊效果,場景自動進行前進旋轉。
監控設備交互效果
當我們點擊場景中的監控設備時可以查看當前設備的運行情況,運行數據等信息。
場景搭建
該系統中的大部分模型都是通過 3dMax 建模生成的,該建模工具可以導出 obj 與 mtl 文件,在 HT 中可以通過解析 obj 與 mtl 文件來生成 3d 場景中的所有複雜模型,當然如果是某些簡單的模型可以直接使用 HT 來繪製,這樣會比 obj 模型更輕量化,所以大部分簡單的模型都是採用 HT for Web 產品輕量化 HTML5/WebGL 建模的方案,具體的解析代碼如下:
1 // 分別為 obj 文件地址,mtl 文件地址 2 ht.Default.loadObj('obj/metro.obj', 'obj/metro.mtl', { 3 center: true, 4 // 模型是否居中,默認為 false,設置為 true 則會移動模型位置使其內容居中 5 r3: [0, -Math.PI / 2, 0], 6 // 旋轉變化參數,格式為 [rx, ry, rz] 7 s3: [0.15, 0.15, 0.15], 8 // 大小變化參數,格式為 [sx, sy, sz] 9 finishFunc: function(modelMap, array, rawS3) { 10 if (modelMap) { 11 ht.Default.setShape3dModel('metro', array); // 註冊一個名字為 metro 的模型 12 } 13 } 14 });
上面通過加載 obj 模型之後註冊了一個名字為 metro 的模型,之後如果要使用該模型可以通過以下代碼來實現:
1 var node = new ht.Node(); 2 node.s({ 3 'shape3d': 'metro' 4 });
上面代碼新建了一個 node 對象,通過設置 style 對象的 shape3d 屬性可以把模型名稱為 metro 用到該 node 對象上去,之後便是我們場景中看到的地鐵列車模型。
動畫代碼分析
地鐵動畫代碼的實現分析
場景中地鐵的運行是通過 HT 提供的調度插件來實現,調度的具體用法可以參考 HT for Web 的調度手冊,該調度主要用於在指定的時間間隔進行函數回調處理,回調函數的第一個參數為 data 圖元,也就是 3D 場景中的模型節點,我們可以判斷當前 data 是否為我們剛才創建的 metro 那個節點來進行後續的操作,場景中模擬了一個左開的地鐵和一個右開的地鐵,兩輛地鐵會交替出現。在 3D 場景中肯定會有坐標系,HT 中是用 x, y, z 來分別表示三個軸,所以地鐵的運動肯定是改變地鐵在坐標系中的位置來實現地鐵的運行,地鐵坐標如下圖所示:
通過上圖可以知道地鐵在 3D 場景中的坐標系,如果要實現地鐵的移動則只需要將地鐵往圖中所示紅色箭頭的方向進行移動,即 x 軸的方向,通過 setX 這個方法不斷的修改地鐵的位置達到地鐵行進的目的,代碼中通過 getSpeedByX 以及 getOpacityByX 兩個方法來不斷獲取此時的列車速度以及列車透明度,以下為關鍵代碼實現:
1 let metroTask = { 2 interval: 50, 3 // 每五十秒執行一次 4 action: (data) = >{ // 即上文所提回調函數 5 // 判斷當時傳進來的節點是否為地鐵列車節點 6 if (data === currentMetro) { 7 // 獲取地鐵此時的 X 軸位置以及行進的方向 8 let currentX = data.getX(), 9 direction = data.a('direction'); 10 // 根據當前的 X 軸位置獲取當前的列車速度 11 let speed = this.getSpeedByX(currentX); 12 // 根據當前的 X 軸位置獲取當前的列車透明度 13 let opacity = this.getOpacityByX(currentX); 14 // 判斷此時 X 軸位置是否超過某個值 即地鐵是在某個範圍內移動 15 if (Math.abs(currentX) <= 5000) { 16 // 設置當前的透明度 17 opacity !== 1 ? currentMetro.s({ 18 'shape3d.transparent': true, 19 'shape3d.opacity': opacity 20 }) : currentMetro.s({ 21 'shape3d.transparent': false 22 }); 23 // 設置當前的 X 軸位置 24 data.setX(currentX + direction * speed); 25 // 判斷此時地鐵的速度為 0,所以此時應該執行開門的動畫 26 if (speed === 0) this.doorAnimation(currentMetro, direction); 27 } 28 // 右方向地鐵開到頭,進行複位 29 if (currentX > 5000 && direction === 1) { 30 currentMetro = leftMetro; 31 currentMetro.setX(5000); 32 } 33 // 左方向地鐵開到頭,進行複位 34 if (currentX < -5000 && direction === -1) { 35 currentMetro = rightMetro; 36 currentMetro.setX( - 5000); 37 } 38 } 39 } 40 }; 41 dm3d.addScheduleTask(metroTask);
通過以上代碼可以知道地鐵在運行的過程中,主要通過修改地鐵的 x 軸位置來產生前進的動畫,並且需要讓地鐵在某個區間內進行運動,需要判斷邊界,而且為了模擬出真實的效果需要根據地鐵當前的位置不斷獲取當前的列車速度以及列車透明度,以下為流程圖:
上圖所示的為地鐵進站時候的流程,當地鐵停靠完畢關門後需要進行出站,此時我們只需要把地鐵位置重新設置一下不為 0 即可,以下為部分代碼實現:
1 currentMetro.setX(direction * 10); // 設置出站列車的位置
當執行上面那句代碼之後上方的 metroTask 調度任務執行到 getSpeedByX 這個方法之後獲取到的 speed 速度不為 0,因此此時會繼續執行地鐵行進的動畫,此時的速度就是由慢至快,透明度由深至淺。以下為開門動畫執行流程:
自動巡檢代碼的實現分析
系統中自動巡檢的實現主要是通過修改 3D 場景中的 eye 以及 center 的值,HT 中提供了 rotate,walk 兩個方法來控制視角的旋轉以及視角的行進,rotate 方法在非第一人稱模式時,旋轉是以 center 為中心進行旋轉,也就是圍繞中心物體旋轉,當為第一人稱時旋轉以 eye 為中心進行旋轉,也就是旋轉眼睛朝向方向。walk 函數同時改變 eye 和 center 的位置,也就是 eye 和 center 在兩點建立的矢量方向上同時移動相同的偏移量。該系統中我沒有採用 rotate 函數而是自己實現了視角的旋轉,因為原本的 rotate 函數旋轉某個角度會馬上旋轉過去而不會有一個旋轉的過程,所以我重新實現了旋轉的方法,該系統中視角旋轉是通過不斷修改 center 的數值來實現,具體實現過程原理如下圖所示:
部分實現代碼如下:
1 rotateStep() { 2 // 即上圖輔助點 C 3 let fromCenter = this.fromCenter; 4 // 即上圖 B 點 5 let toCenter = this.toCenter; 6 // 每幀轉一度 7 let rotateValue = this.rotateFrame || Math.PI / 180; 8 // 輔助點 C 與 B 點之間建立一個方向向量 9 let centerVector = new ht.Math.Vector2(toCenter.x - fromCenter.x, toCenter.y - fromCenter.y); 10 let centerVectorLength = centerVector.length(); 11 // 此時旋轉百分比 12 let rotatePercent = rotateValue * this.stepNum / this.curRotateVal; 13 if (rotatePercent >= 1) { 14 rotatePercent = 1; 15 this.stepNum = -2; 16 } 17 let newLength = rotatePercent * centerVectorLength; 18 centerVector.setLength(newLength); 19 let newCenterVector = centerVector.add(fromCenter); 20 // 獲取旋轉過程中 center 的點信息 21 let newCenterPosition = [newCenterVector.x, this.personHeight, newCenterVector.y]; 22 // 設置當前 center 的大小 23 this.g3d.setCenter(newCenterPosition); 24 }
通過上述代碼就實現了場景中的視角旋轉,並且可以通過修改 rotateValue 的值控制旋轉的速度。
電梯動畫代碼的實現分析
場景中電梯是一個 obj 模型,3D 模型是由最基礎的三角形面拼接合成,例如 1 個矩形可以由 2 個三角形構成,1 個立方體由 6 個面即 12 個三角形構成,以此類推更複雜的模型可以由許多的小三角形組合合成。因此 3D 模型定義即為對構造模型的所有三角形的描述,而每個三角形由三個頂點 vertex 構成,每個頂點 vertex 由 x, y, z 三維空間坐標決定,HT 中使用 vs 數組記錄構成三角面的所有頂點坐標,所以如果想要讓電梯運行起來,只需要把所有的頂點坐標往電梯運行的方向進行平移,以下為部分關鍵偽代碼:
1 // vs 指的是構成電梯模型所有的三角面頂點坐標數組 2 // 由於場景中電梯的運行方向為往對角線右上方運動,所以只需要修改 x 軸以及 y 軸坐標值 3 // xStep yStep 為每次電梯運動的距離 4 setInterval(() = >{ 5 // i+3 是因為 vs 數組的順序為 x, y, z 軸 所以每次 i 偏移三個單位大小 6 for (let i = 0, l = vs.length; i < l; i = i + 3) { 7 // 該頂點坐標下一個 x 軸坐標的值 8 let nextX = vs[i] - xStep; 9 // 該頂點坐標下一個 y 軸坐標的值 10 let nextY = vs[i + 1] + yStep; 11 vs[i] = nextX < -0.5 ? 0.5 - (Math.abs(nextX) - 0.5) : nextX; 12 vs[i + 1] = nextY > 0.5 ? -0.5 + (Math.abs(nextY) - 0.5) : nextY; 13 } 14 }, 15 200);
電梯運動動畫如下圖所示:
監控功能展示及介紹
視頻監控
當點擊場景中的攝像頭之後右側頂部會顯示出當前攝像頭的監控畫面,以下為實現效果圖:
煙霧報警監控
煙霧報警會根據後台實時傳遞過來的狀態值來變換當前煙霧報警模型的顏色,紅色為報警狀態,以下為實現效果圖:
電視列車到站時間監控
日常地鐵站中會有專門的電視來展示下一班地鐵到站的時間表,該系統中也模擬該效果,不過該系統暫時做了電視的模型,時間暫無對接,以下為效果圖:
場景監控交互
3D 場景中交互是比較簡單的,主要是點擊攝像頭展示 2D 監控面板,在 2D 界面中主要是切換三種交互模式,三種交互模式為互斥的關係,以下是 3D 交互註冊事件代碼:
1 g3d.mi((e) = >{ 2 let { 3 g2d, 4 dm2d 5 } = this; 6 // 為點擊類型 7 if (e.kind === 'clickData') { 8 // data 為當前點擊的圖元 9 let data = e.data; 10 // 當前圖元的 shape3d 類型 11 let shape3d = data.s('shape3d'); 12 // 判斷當前 shape3d 類型是否為攝像頭 13 if (shape3d && shape3d.indexOf('攝像頭') > 0) { 14 let cameraPanel = dm2d.getDataByTag('cameraPanel'); 15 // toggle 切換攝像頭 2d 面板 16 g2d.isVisible(cameraPanel) ? cameraPanel.s('2d.visible', false) : cameraPanel.s('2d.visible', true); 17 } 18 } 19 // 為點擊 3d 場景背景類型 20 if (e.kind === 'clickBackground') { 21 let cameraPanel = dm2d.getDataByTag('cameraPanel'); 22 // 隱藏攝像頭 2d 面板 23 g2d.isVisible(cameraPanel) && cameraPanel.s('2d.visible', false); 24 } 25 });
總結
工業互聯網將人,數據和機器連接起來,地鐵站 3D 可視化系統則是一個很好的展現,HT 的輕量化,數據的可視化,機器的可視化,資產的管理化幫助我們更好的監控。而物聯網將通過各種信息傳感設備,實時採集任何需要監控、連接、互動的物體或過程等各種需要的信息,通過與 HT 的結合更好的展現出可視化的優勢,當然地鐵站還可以與 VR 進行結合,在各地科技展會中我們可以見到各種 VR 場景操作,HT 中也可以結合 VR 設備進行操作,可以戴上設備在地鐵站中漫遊,讓人有身臨其境的感覺,由於場景本身的輕量化,所以 VR 場景下的流暢性也是十分的高,讓用戶不會有頭暈的感覺。當然系統本身也可以在移動端運行,以下為移動端運行截圖:
程序運行截圖: