基於 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 中提供了 rotatewalk 兩個方法來控制視角的旋轉以及視角的行進,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 場景下的流暢性也是十分的高,讓用戶不會有頭暈的感覺。當然系統本身也可以在移動端運行,以下為移動端運行截圖:

程式運行截圖: