基於 HTML5 和 WebGL 的地鐵站 3D 視覺化系統
前言
工業網際網路,物聯網,視覺化等名詞在我們現在資訊化的大背景下已經是耳熟能詳,日常生活的交通,出行,吃穿等可能都可以用資訊化的方式來為我們表達,在傳統的視覺化監控領域,一般都是基於 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 場景下的流暢性也是十分的高,讓使用者不會有頭暈的感覺。當然系統本身也可以在移動端執行,以下為移動端執行截圖:
程式執行截圖: