H5 + WebGL 實現的樓宇自控 3D 可視化監控

  • 2019 年 11 月 8 日
  • 筆記

 

前言

智慧樓宇和人們的生活息息相關,樓宇智慧化程度的提高,會極大程度的改善人們的生活品質,在當前工業互聯網大背景下受到很大關注。目前智慧樓宇可視化監控的主要優點包括:

  • 智慧化 — 智慧樓宇是一個生態系統,像人一樣擁有感知能力、自我判斷能力以及控制能力。
  • 綠色化 — 綠色建築在耗能、產能以及能源管理方面實現綠色化,樓宇安防實現綠色化監控。
  • 運行成本可控制 — 基於可持續發展的要求,現代建築、商業建築需運行50年以上,建築在運行過程中能源消耗巨大,如何降低運營成本降低,使建築在低碳、環保的狀態下健康運行。

傳統的 智慧樓宇/樓宇自動化/樓宇安防/智慧園區 常會採用 BIM(建築資訊模型 Building information modeling)軟體,如 Autodesk 的 Revit 或 Bentley 這類建築和工程軟體,但這些 BIM 建模模型的數據往往過於龐大臃腫,絕大部分細節資訊對樓宇自控意義不大,反而影響拖累了行業 Web SCADA 或 Web 組態監控的趨勢,所以我們採用以 Hightopo 的 HT for Web 產品輕量化 HTML5/WebGL 建模的方案,實現快速建模、運行時輕量化到甚至手機終端瀏覽器即可 3D 可視化運維的良好效果。

本篇文章通過對智慧建築的建模,頁面動畫效果的實現,以及頁面主要功能點進行闡述,幫助我們了解如何使用 HT 實現一個簡單的智慧樓宇可視化監控,以及幫助我們了解智慧樓宇,樓宇自動化的優勢。

預覽地址:基於 HTML5 的 WebGL 樓宇自控 3D 可視化監控 http://www.hightopo.com/demo/ht-smart-building/

介面簡介及效果預覽

介面通過 2d 圖紙疊加在 3d 場景上來實現 2d 介面 與 3d 場景的融合,2d 介面通過自動布局的機制實現了手機端與電腦端的響應式呈現。

介面初始化效果

 

介面初始化過程中的動畫包括地面路徑的實時渲染,樓層的展開,樓層的輝光掃描,樓層報警點動態水波,樓層監測數據面板的實時變化等等。

監控介面效果

監控介面包括:

  1. 人員進入大廈的實時監控,面板中動態刷新人員進入大廈的頭像以及當前大廈人數等實時資訊。
  2. 大廈電梯運行情況實時監控,系統中展示電梯當前的運行位置以及是否在運行等資訊。
  3. 大廈某個具體樓層監控數據的實時監控,通過柱狀圖的形式展示了當前樓層具體資訊的大小。
  4. 大廈管道的實時監控,展示了當前智慧建築所有管道的運行情況。

智慧建築建模

該 3d 場景中所有的模型均為線段和六面體搭建,相比較通過 3d Max 建模然後通過 obj 導入來說場景中的三角面會少很多,更加的輕量化,例如場景中建築的樓層,通過 ht.Shape 類繪製,該類中記錄著樓層 points 點的資訊以及 segments 為 ht.List 類型的線段數組資訊,segments 代表著點的連接方式,用於告訴 ht.Shape 利用點的資訊來繪製二次貝塞爾曲線或者三次貝塞爾曲線或者直線等資訊,相關具體說明請參考 HT for Web 的[形狀手冊],以下為繪製單層的截圖:

通過上圖可以知道構建完一層模型之後其它幾層模型均為相同的,只是 y 軸的數值不同,通過疊加幾層之後便可形成一幢大樓的輪廓。如果用戶需要搭建智慧園區,智慧樓宇等場景,完全可以使用這種基於 HTML5/WebGL 建模的方案,減少考慮使用 BIM 建模模型。場景中的管道以及背景地圖路線都為點連線之後構成,只是通過修改線的顏色粗細或者進行貼圖來修改線或者面的樣式,場景中的電梯為一個顏色為黃色的簡單六面體,電梯線也為一條線段而已,所以場景中的模型都是輕量化的建模,使 3d 場景運行渲染的更加流暢,提升用戶體驗。

場景關鍵動畫程式碼分析

1. 地圖路線動畫程式碼分析

通過上述智慧建築建模的分析我們可以知道線路都是為點與點之間進行連線而生成,所以當我們繪製完地圖的路徑之後可以得到所有點的資訊,假如直線 AB 為地圖中的某一條線段,那麼我們可以知道點 A 以及點 B 的點的坐標,之後我們可以計算 AB 線段上任意一點 C 的點的坐標,然後通過連接 A 點與 C 點來形成一條與 AB 線段位置方向相同但是大小比 AB 線段短的線,直到 AC 線段的長度等於 AB 線段長度之後再進行下一條路徑動畫的繪製,以下為關鍵偽程式碼展示:

複製程式碼
 1  // currentIndex 為當前路徑繪製到的點的索引    2  // points 為當前路徑所有點的資訊  currentPoints 為繪製過程中點的資訊    3  // segments 為當前路徑所有點的連接方式資訊 currentSegments 為繪製過程中點的連接方式資訊   4   5  // 即上述此時 A 點資訊   6 let fromPoint = points[currentIndex];   7  // 即上述此時 B 點資訊   8 let toPoint = points[currentIndex + 1];   9  // 通過 AB 兩點資訊組成一條 AB 方向的向量  10 let pointVector = new ht.Math.Vector2(toPoint.x - fromPoint.x, toPoint.y - fromPoint.y);  11  // 記錄該向量的長度,用於判斷 AC 是否大於等於 AB  12 let pointVectorLength = pointVector.length();  13  14 let currentPoints = [], currentSegments = [];  15  16 for(let i = 0; i < currentIndex + 1; i++) {  17     currentPoints.push({  18       x: points[i].x,  19       y: points[i].y  20     });  21     currentSegments.push(segments[i]);  22 }
複製程式碼

通過上述程式碼可以知道我們獲取到了 currentPoints 以及 currentSegments 的資訊了,之後便要計算在 fromPoint(A點) 與 toPoint(B點) 連線上點的坐標,即 C 點,以下為計算 C 點的關鍵偽程式碼:

複製程式碼
 1  // addLength 為每次增加的線段長度值,該程式中使用 500 即每次長度增加 500   2 let nextVectorLength = currentVectorLength + addLength,   3     tempPoint;   4   5 roadData.currentVectorLength = nextVectorLength;   6   7  // 判斷 AC 線段的長度是否大於 AB    8 if(nextVectorLength > pointVectorLength) {   9     nextVectorLength = pointVectorLength;  10     roadData.currentVectorLength = 0;  11     roadData.currentIndex = currentIndex + 1;  12 }  13  14 pointVector.setLength(nextVectorLength);  15  16  // 即為 C 點坐標  17 tempPoint = {x: pointVector.x + fromPoint.x, y: pointVector.y + fromPoint.y};  18  // 往 currentPoints 添加 C 點坐標  19 currentPoints.push(tempPoint);  20  // 往 currentSegments 添加 C 點連接方式,此程式中都為直線連接,所以值都為 2  21 currentSegments.push(2);  22  // roadNode 即為 ht.Shape 類 重新設置 ht.Shape 類點的資訊  23 roadNode.setPoints(currentPoints);  24  // 重新設置 ht.Shape 類點的連接資訊  25 roadNode.setSegments(currentSegments); 
複製程式碼

以下為動畫程式碼執行流程圖

以下為繪製一條路線動畫的截圖:

程式中通過向量的計算方式來不斷獲取 C 點的坐標,當然也可以用其它方式來計算 C 點的坐標。

2. 水波以及掃描動畫程式碼分析

水波以及掃描動畫都是通過 HT 提供的修改圖標矩形框資訊 api 來進行控制,通過調度的方式不斷修改圖標矩形框大小來產生水波以及掃描的動畫效果,調度的具體用法可以參考 HT for Web 的[調度手冊],以下為水波動畫的關鍵偽程式碼:

複製程式碼
 1 // waterWaveNodes 所有水波節點   2 let waterWaveTask = {   3     interval: 100, // 指每隔 100 ms 調用 action 函數一次   4     action: function(data){   5         // 判斷 waterWaveNodes 是包含 data   6         if(waterWaveNodes.indexOf(data) > -1) {   7             // 獲取此時圖標矩形框資訊 circleRect 是個長度為 4 的數組 分別表示 x, y, width, height   8             let circleRect = data.a('circleRect');   9  10             if(circleRect) {  11                 // 通過修改高度來變大水波大小  12                 let nextHeight = circleRect[3] + 10;  13  14                 // 高度最大值為 250   15                 if(nextHeight < 250) {  16                      // 對應修改 y 的大小,y 的增加大小為高度的一半  17                     circleRect[1] = circleRect[1] - 5;  18                     circleRect[3] = nextHeight;  19                     data.a('circleRect', circleRect);  20                     data.a('borderColor', 'rgba(255, 133, 133, ' + (1 - circleRect[3] / 250) + ')');  21                 }  22                 else {  23                     data.a('circleRect', [-0.5,128,257,0]);  24                     data.a('borderColor', 'rgba(255, 133, 133)');  25                 }  26  27             }  28             else {  29                 data.a('circleRect', [-0.5,128,257,0]);  30             }  31  32         }  33     }  34 };  35 dm3d.addScheduleTask(waterWaveTask); // 新增該調度任務
複製程式碼

下圖為水波在 2d 中的截圖:

3. 數字變化動畫程式碼分析

從程式的截圖中可以看到在 2d 面板以及 3d 場景中都有數字在動態的變化,這部分主要通過數據綁定動態來修改數值的大小,關於數據綁定可以參考 HT for Web 的[數據綁定手冊],也是通過調度來不斷修改數值的大小,程式中我封裝了產生隨機數的程式碼,用於每次產生隨機數之後綁定到對應的節點上,以下為修改 2d 面板上數字的變化偽程式碼:

複製程式碼
 1  // numNode(1-7) 為 2d 面板中對應數字的節點   2  // data.a('ht.value', number) 即為動態修改 attr 上的 ht.value 資訊,之後圖紙會自動更新新賦予的數值   3  // getRandomValue 為自己封裝的產生隨機數的方法   4 this.change2dNumTask = {   5         interval: 1000,   6         action: (data) => {   7             if(data === numNode1 || data === numNode2) {   8                 data.a('ht.value', util.getRandomValue([500, 999], 5));   9             }  10             if(data === numNode3 || data === numNode4) {  11                 data.s('text', util.getRandomValue([0, 30], 2) + '%');  12             }  13             if(data === numNode5) {  14                 data.a('ht.value', util.getRandomValue([0, 99999], 5, 3));  15             }  16             if(data === numNode6) {  17                 data.a('ht.value', util.getRandomValue([0, 100], 2));  18             }  19             if(data === numNode7) {  20                 data.a('ht.value', util.getRandomValue([0, 100], 2));  21             }  22        }  23     };  24 dm2d.addScheduleTask(this.change2dNumTask); // 新增該調度任務
複製程式碼

通過以上程式碼可以知道修改數值是通過修改節點的 attr 以及 style 對象的某個屬性值來動態變化數值,當然在程式中 2d 面板可能還會隱藏,此時該調度任務就不需要執行,可以調用 removeScheduleTask 方法來移除此調度任務。

4. 柱狀圖高度動畫程式碼分析

在 3d 場景中柱狀體也是一個六面體,只是四周用了漸變的貼圖,以及頂面用了一張純色的貼圖構造出來,每個六面體都有高度的資訊,HT 中通過 node.getTall() 來獲取當前六面體的高度值,根據上一節講的數據綁定,我們可以在展示柱狀圖的時候循環獲取所有柱狀體節點的高度值大小假如命名為 tall,之後通過 node.a(‘tall’, tall) 將該值存儲到當前柱狀圖節點的 attr 對象上面,之後在柱狀體初始化的時候可以不斷修改高度值來動態改變高度,當高度值大於 node.a(‘tall’) 則說明當前柱狀體初始化的高度已經完成。以下為相關的偽程式碼:

複製程式碼
 1 charts.forEach((chart) => {   2     !chart.a('tall') && chart.a('tall', chart.getTall()); // 將高度存儲到 attr 上   3     chart.setTall(0); // 設置初始高度為 0   4 });   5 this.chartAnimteTask = {   6         interval: 25,   7         action: function(data){   8             if(charts.indexOf(data) > -1) {   9                 if(finishNum !== chartLength) {  10                     if(data.getTall() !== data.a('tall')) {  11                         let nextTall = data.getTall() + deep; // deep 為每次增加的高度  12                         let tall = data.a('tall'); // 獲取初始化高度   13                         // 判斷下一個高度是否大於初始化高度  14                         if(nextTall < tall) {  15                             data.setTall(nextTall);  16                         }  17                         else {  18                             data.setTall(tall);  19                             finishNum++;  20                         }  21                     }  22                 }  23             }  24         }  25 };  26 dm3d.addScheduleTask(this.chartAnimteTask); // 新增該調度任務
複製程式碼

通過上面程式碼可以知道動畫每一步的程式執行也是通過調度來完成的,與前文幾個動畫的實現方式類似。

5. 3d 鏡頭推進程式碼分析

3d 場景中視野的推進後退都是通過 HT api 提供的修改 eye 以及 center 的數值方法來實現,通過不斷調用 setEye 以及 setCenter 方法來達到修改視角的目的,eye 類比人眼睛所處的位置,center 類比人眼睛聚焦的位置,以下為實現鏡頭推進關鍵的偽程式碼:

複製程式碼
 1 let e = ht.Default.clone(g3d.getEye()), // 獲取當前眼睛的位置   2     c = ht.Default.clone(g3d.getCenter()); // 獲取當前眼睛聚焦的位置   3  // eye 為需要修改的對應 eye 值   4  // center 為需要修改的對應 center 值   5  // 以下為分別獲取 eye 與 center 在 xyz 三個坐標軸之間的差值   6     let edx = eye[0] - e[0],   7         edz = eye[1] - e[1],   8         edy = eye[2] - e[2],   9         cdx = center[0] - c[0],  10         cdz = center[1] - c[1],  11         cdy = center[2] - c[2];  12     // 開啟不斷修改 eye 與 center 的動畫  13     ht.Default.startAnim({  14         duration: time ? time : 3000,  15         easing: function(t){ return t; },  16         finishFunc: function() {  17             if(typeof cb === 'function') {  18                 cb();  19             }  20         },  21         action: function (v) {  22             // v 為從 0-1 變換的值   23             g3d.setEye([  24                 e[0] + edx * v,  25                 e[1] + edz * v,  26                 e[2] + edy * v  27             ]);  28             g3d.setCenter([  29                 c[0] + cdx * v,  30                 c[1] + cdz * v,  31                 c[2] + cdy * v  32             ]);  33         }  34 });
複製程式碼

通過以上程式碼可以知道通過修改 eye 與 center 分別對應的 xyz 軸的值與當前 e 與 c 分別對應的 xyz 軸的值之間的距離來達到視角的變化。

以下為該程式碼的一個應用截圖:

總結

物聯網通過各種資訊感測設備,實時採集任何需要監控、連接、互動的物體或過程等各種需要的資訊,與互聯網結合形成的一個巨大網路。實現了物與物、物與人,所有的物品與網路的連接,方便識別、管理和控制。所以物聯網帶給我們的智慧樓宇的可視化監控需要監控的方面可能還有很多,該系統中針對人員出入,設備資訊,管道資訊等的監控實現了一個簡單的智慧樓宇監控系統,物聯網也將用戶端延伸和擴展到了任何物品與物品之間,讓我們更加了解搭建智慧園區,智慧校園等場景監控之後設備可視化,資產可視化帶給我們的直觀性。場景中的反光與景深等效果都是 HT 核心包提供的效果,所有的模型搭建與動畫也都是通過 HT 核心包提供的 api 進行建模與動畫驅動,所以在網頁中展示會十分流暢,大大提高了用戶的體驗,並且在移動端表現也十分友好。

以下為移動端的程式運行截圖:

程式運行截圖: