基於 HTML5 的 WebGL 樓宇自控 3D 視覺化監控

圖撲軟體發表於2019-02-22

前言

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

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

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

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

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

介面簡介及效果預覽

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

介面初始化效果

在這裡插入圖片描述
介面初始化過程中的動畫包括地面路徑的實時渲染,樓層的展開,樓層的輝光掃描,樓層報警點動態水波,樓層監測資料皮膚的實時變化等等。

監控介面效果

在這裡插入圖片描述
監控介面包括:

人員進入大廈的實時監控,皮膚中動態重新整理人員進入大廈的頭像以及當前大廈人數等實時資訊。
大廈電梯執行情況實時監控,系統中展示電梯當前的執行位置以及是否在執行等資訊。
大廈某個具體樓層監控資料的實時監控,通過柱狀圖的形式展示了當前樓層具體資訊的大小。
大廈管道的實時監控,展示了當前智慧建築所有管道的執行情況。

智慧建築建模

該 3d 場景中所有的模型均為線段和六面體搭建,相比較通過 3d Max 建模然後通過 obj 匯入來說場景中的三角面會少很多,更加的輕量化,例如場景中建築的樓層,通過 ht.Shape 類繪製,該類中記錄著樓層 points 點的資訊以及 segmentsht.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 進行建模與動畫驅動,所以在網頁中展示會十分流暢,大大提高了使用者的體驗,並且在移動端表現也十分友好。

以下為移動端的程式執行截圖:
在這裡插入圖片描述
程式執行截圖:
在這裡插入圖片描述
在這裡插入圖片描述

相關文章