現代工業化的推進在極大加速現代化進程的同時也帶來的相應的安全隱患,在傳統的可視化監控領域,一般都是基於 Web SCADA 的前端技術來實現 2D 可視化監控,本系統採用 Hightopo 的 HT for Web 產品來構造輕量化的 3D 可視化場景,該 3D 場景從正面展示了一個現代化工廠的現實... ...
前言
現代工業化的推進在極大加速現代化進程的同時也帶來的相應的安全隱患,在傳統的可視化監控領域,一般都是基於 Web SCADA 的前端技術來實現 2D 可視化監控,本系統採用 Hightopo 的 HT for Web 產品來構造輕量化的 3D 可視化場景,該 3D 場景從正面展示了一個現代化工廠的現實場景,包括工廠工人的實時位置、電子圍欄的範圍、現場的安全情況等等,幫助我們直觀的瞭解當前工廠人員的安全狀況。
本篇文章通過對工廠可視化場景的搭建和模型的載入,人物實時定位代碼的實現、電子圍欄和軌跡圖的實現進行闡述,幫助我們瞭解如何通過使用HT實現一個簡單的3D電子圍欄可視化。
以下是項目地址:基於HTML5 WebGL的工業化3D電子圍欄、軌跡圖
效果預覽
工廠人員實時定位效果及電子圍欄效果
軌跡圖效果圖
代碼實現
人物模型及場景
項目中使用的人物模型是通過 3dMax 建模生成的,該建模工具可以導出 obj 與 mtl 文件,在 HT 中可以通過解析 obj 與 mtl 文件來生成 3d 場景中的攝像頭模型。
項目中場景通過 HT 的 3d 編輯器進行搭建,場景中的模型有些是通過 HT 建模,有些通過 3dMax 建模,之後導入 HT 中。
繪製電子圍欄
場景中的電子圍欄並不是使用3dMax搭建的模型,HT提供了多種基礎形體類型供用戶建模使用,不同於傳統的3D建模方式,HT的建模核心都是基於API的介面方式, 通過預定義的圖元類型和參數介面,進行設置達到三維模型的構建。根據形狀,我將電子圍欄分成圓柱、長方體和底部為多邊形的棱柱。
以下是我繪製電子圍欄的相關偽代碼:
1 G.makeShapes = function (data, typeName, color, lastColor, g3dDm) { 2 //data是包含電子圍欄圖形信息的json對象數組 3 let shapes = data; 4 for (let i = 0; i < shapes.length; i++) { 5 let shape = shapes[i]; 6 let type = Number(shape['type']); 7 let x = Number(shape['x']); 8 let y = Number(shape['y']); 9 let z = Number(shape['z']); 10 let width = Number(shape['width']); 11 let height = Number(shape['height']); 12 let tall = Number(shape['tall']); 13 let radius = Number(shape['radius']); 14 let vertexX = shape['vertexX']; 15 let vertexY = shape['vertexY']; 16 let nodePoints = []; 17 let p3 = []; 18 let s3 = []; 19 let centerX = 0; 20 let centerY = 0; 21 let centerZ = 0; 22 let node = new ht.Node(); 23 node.setTag(typeName + i); 24 switch (type) { 25 //第一種形狀:圓柱 26 case 1: 27 p3 = [-x, tall / 2, -y]; 28 s3 = [radius, tall, radius]; 29 //定義電子圍欄樣式 30 node.s({ 31 "shape3d": "cylinder", 32 "shape3d.color": color, 33 "shape3d.transparent": true, 34 "shape3d.reverse.color": color, 35 "shape3d.top.color": color, 36 "shape3d.top.visible": false, 37 "shape3d.bottom.color": color, 38 "shape3d.from.color": color, 39 "shape3d.to.color": color 40 }); 41 node.p3(p3); //設置三維坐標 42 node.s3(s3); //設置形狀信息 43 break; 44 //第二種形狀:長方體 45 case 2: 46 centerX = x - width / 2; 47 centerY = y - height / 2; 48 centerZ = z + tall / 2; 49 p3 = [-Number(centerX) - width, Number(centerZ), -Number(centerY) - height]; 50 s3 = [width, tall, height]; 51 node.s({ 52 "all.color": color, 53 "all.reverse.color": color, 54 "top.visible": false, 55 "all.transparent": true 56 }); 57 node.p3(p3); 58 node.s3(s3); 59 break; 60 //第三種形狀:底部為不規則形狀的等高體 61 case 3: 62 let segments = []; 63 for (let i = 0; i < vertexX.length; i++) { 64 let x = -vertexX[i]; 65 let y = -vertexY[i]; 66 let newPoint = { x: x, y: y }; 67 nodePoints.push(newPoint); 68 //1: moveTo,占用1個點信息,代表一個新路徑的起點 69 if (i === 0) { 70 segments.push(1); 71 } 72 else { 73 //2: lineTo,占用1個點信息,代表從上次最後點連接到該點 74 segments.push(2); 75 if (i === vertexX.length - 1) { 76 //5: closePath,不占用點信息,代表本次路徑繪製結束,並閉合到路徑的起始點 77 segments.push(5); 78 } 79 } 80 } 81 node = new ht.Shape(); 82 node.setTag(typeName + i); 83 node.s({ 84 'shape.background': lastColor, 85 'shape.border.width': 10, 86 'shape.border.color': lastColor, 87 'all.color': lastColor, 88 "all.transparent": true, 89 'all.opacity': 0.3, 90 }); 91 p3 = [nodePoints[0]['x'], tall / 2, nodePoints[0]['y']]; 92 node.p3(p3); 93 node.setTall(tall); 94 node.setThickness(5); 95 node.setPoints(nodePoints); //node設置點集位置信息 96 node.setSegments(segments); //node設置點集連接規則 97 break; 98 } 99 g3dDm.add(node); 100 } 101 }
考慮到電子圍欄在某些情況下可能會影響到對人物位置的觀察,設置了隱藏電子圍欄的功能。在HT中用戶可以自定義設置標簽Tag作為模型唯一的標識,我將所有的電子圍欄模型的標簽首碼都統一併且保存在fenceName中,需要隱藏的時候則遍歷所有標簽名稱首碼為fenceName的模型,並且根據模型種類的不同設置不同的隱藏方式。
以下是相關偽代碼:
1 g3dDm.each((data) => { 2 if (data.getTag() && data.getTag().substring(0, 4) === fenceName) { 3 if (data.s('all.opacity') === '0') { 4 data.s('all.opacity', '0.3'); 5 } 6 else { 7 data.s('shape3d.visible', true); 8 data.s('all.visible', true); 9 data.s("2d.visible", true); 10 data.s("3d.visible", true); 11 } 12 } 13 });
人物模型實時定位
因為項目使用的是http協議獲取數據,因此使用定時器定時刷新人物數據信息,HT有設置節點位置的setPosition3d方法,因此不做過多介紹,但是人物節點的位置的刷新還包括人物的朝向,因此每次人物移動都需要和上次位置進行比對,計算出偏移的角度。
相關偽代碼如下:
1 // 刷新數據的人物結點與原來的人物節點標簽相同,則存在做位置更新 2 if (realInfoData.tagId === tag.getTag()) { 3 //計算位置朝向偏移參數 4 let angleNumber = Math.atan2(((-p3[2]) - (-tag.p3()[2])), ((-p3[0]) - (-tag.p3()[0]))); 5 //如果在原地就不轉向,判斷人物在平面位置是否發生變化 6 if (p3[0] !== tag.p3()[0] || p3[2] !== tag.p3()[2]) { 7 if (angleNumber > 0) { 8 angleNumber = Math.PI - angleNumber; 9 } else { 10 angleNumber = -Math.PI - angleNumber; 11 } 12 //設置人物朝向 13 tag.setRotation3d(0, angleNumber + Math.PI / 2, 0); 14 } 15 //設置人物位置 16 tag.p3(p3); 17 }
人物觸發警報
當人物觸發警報時,有2種方式同時提醒系統使用者。一是人物頭上的面板顏色發生改變,並且顯示報警信息。
相關代碼如下:
1 switch(obj.alarmType){ 2 case null: 3 if(panel){//無警報 4 panel.a('alarmContent',''); 5 panel.a('bg','rgba(6,13,36,0.80)'); 6 } 7 break; 8 case '0': 9 panel.a('alarmContent','進入圍欄'); 10 panel.a('bg','rgb(212,0,0)'); 11 break; 12 case '1': 13 panel.a('alarmContent','SOS'); 14 panel.a('bg','rgb(212,0,0)'); 15 break; 16 case '2': 17 panel.a('alarmContent',''); //離開圍欄 18 panel.a('bg','rgba(6,13,36,0.80)'); 19 break; 20 case '3': 21 panel.a('alarmContent','長時間未動'); 22 panel.a('bg','rgb(212,0,0)'); 23 break; 24 }
二是頁面的右側面板會增加警報信息。
相關代碼如下:
1 data.a('text', info); 2 list.dm().add(data);
軌跡圖軌跡實現原理
在發生警報後,需要根據人物的軌跡圖回溯發生警報的來龍去脈。如果使用根據點集每走一步就繪製一個canvas腳步節點的方式去重現軌跡,很容易造成節點繪製過多,頁面卡頓的情況,因此我使用一整條管道的方式代替一個人物的所有腳步節點,使用管道的好處是,每個人物的軌跡圖從開始到結束只有一個管道的圖元信息,因此對頁面的渲染更加友好和流暢。
生成管道軌跡的代碼如下:
1 //生成軌跡 2 this.ployLines[i] = new ht.Polyline(); 3 this.ployLines[i].setParent(node); 4 this.points[i] = []; 5 this.points[i].push({ x: p3[0], y: p3[2], e: p3[1] -50 }); 6 this.ployLines[i].setPoints(this.points[i]); 7 this.ployLines[i].s({ 8 'shape.border.color': 'red' 9 }); 10 g3dDm.add(this.ployLines[i]);
人物前進一步,則往管道的點集中推進一個點的坐標,同時繪製新的管道部分。同理,人物後退一步,則管道的點集中推出當前最後一個點的坐標,同時管道失去最後兩點連接的部分。另外我通過使用定時器,對軌跡圖的前進和後退分別做了快進和快退的處理。以下為軌跡圖的運行效果: