基於H5與webGL的 3D 電子圍欄展現

前言

現代工業化的推動在極大加速現代化進程的同時也帶來的相應的安全隱患,在傳統的可視化監控領域,通常都是基於 Web SCADA 的前端技術來實現 2D 可視化監控,本系統採用 Hightopo 的 HT for Web 產品來構造輕量化的 3D 可視化場景,該 3D 場景從正面展現了一個現代化工廠的現實場景,包括工廠工人的實時位置、電子圍欄的範圍、現場的安全狀況等等,幫助咱們直觀的瞭解當前工廠人員的安全情況。html

本篇文章經過對工廠可視化場景的搭建和模型的加載,人物實時定位代碼的實現、電子圍欄和軌跡圖的實現進行闡述,幫助咱們瞭解如何經過使用HT實現一個簡單的3D電子圍欄可視化。前端

如下是項目地址:基於HTML5 WebGL的工業化3D電子圍欄軌跡圖node

效果預覽

工廠人員實時定位效果及電子圍欄效果

20191008222629591.gif

軌跡圖效果圖

軌跡圖

代碼實現

人物模型及場景

項目中使用的人物模型是經過 3dMax 建模生成的,該建模工具能夠導出 obj 與 mtl 文件,在 HT 中能夠經過解析 obj 與 mtl 文件來生成 3d 場景中的攝像頭模型。json

項目中場景經過 HT 的 3d 編輯器進行搭建,場景中的模型有些是經過 HT 建模,有些經過 3dMax 建模,以後導入 HT 中。canvas

繪製電子圍欄

場景中的電子圍欄並非使用3dMax搭建的模型,HT提供了多種基礎形體類型供用戶建模使用,不一樣於傳統的3D建模方式,HT的建模核心都是基於API的接口方式, 經過預約義的圖元類型和參數接口,進行設置達到三維模型的構建。根據形狀,我將電子圍欄分紅圓柱、長方體和底部爲多邊形的棱柱。segmentfault

如下是我繪製電子圍欄的相關僞代碼:數組

G.makeShapes = function (data, typeName, color, lastColor, g3dDm) {
    //data是包含電子圍欄圖形信息的json對象數組
    let shapes = data;
    for (let i = 0; i < shapes.length; i++) {
        let shape = shapes[i];
        let type = Number(shape['type']);
        let x = Number(shape['x']);
        let y = Number(shape['y']);
        let z = Number(shape['z']);
        let width = Number(shape['width']);
        let height = Number(shape['height']);
        let tall = Number(shape['tall']);
        let radius = Number(shape['radius']);
        let vertexX = shape['vertexX'];
        let vertexY = shape['vertexY'];
        let nodePoints = [];
        let p3 = [];
        let s3 = [];
        let centerX = 0;
        let centerY = 0;
        let centerZ = 0;
        let node = new ht.Node();
        node.setTag(typeName + i);
        switch (type) {
            //第一種形狀:圓柱
            case 1:
                p3 = [-x, tall / 2, -y];
                s3 = [radius, tall, radius];
                //定義電子圍欄樣式
                node.s({
                    "shape3d": "cylinder",
                    "shape3d.color": color,
                    "shape3d.transparent": true,
                    "shape3d.reverse.color": color,
                    "shape3d.top.color": color,
                    "shape3d.top.visible": false,
                    "shape3d.bottom.color": color,
                    "shape3d.from.color": color,
                    "shape3d.to.color": color
                });
                node.p3(p3);    //設置三維座標
                node.s3(s3);    //設置形狀信息
                break;
            //第二種形狀:長方體
            case 2:
                centerX = x - width / 2;
                centerY = y - height / 2;
                centerZ = z + tall / 2;
                p3 = [-Number(centerX) - width, Number(centerZ), -Number(centerY) - height];
                s3 = [width, tall, height];
                node.s({
                    "all.color": color,
                    "all.reverse.color": color,
                    "top.visible": false,
                    "all.transparent": true
                });
                node.p3(p3);
                node.s3(s3);
                break;
            //第三種形狀:底部爲不規則形狀的等高體
            case 3:
                let segments = [];
                for (let i = 0; i < vertexX.length; i++) {
                    let x = -vertexX[i];
                    let y = -vertexY[i];
                    let newPoint = { x: x, y: y };
                    nodePoints.push(newPoint);
                    //1: moveTo,佔用1個點信息,表明一個新路徑的起點
                    if (i === 0) {
                        segments.push(1);
                    }
                    else {
                        //2: lineTo,佔用1個點信息,表明從上次最後點鏈接到該點
                        segments.push(2);
                        if (i === vertexX.length - 1) {
                            //5: closePath,不佔用點信息,表明本次路徑繪製結束,並閉合到路徑的起始點
                            segments.push(5);
                        }
                    }
                }
                node = new ht.Shape();
                node.setTag(typeName + i);
                node.s({
                    'shape.background': lastColor,
                    'shape.border.width': 10,
                    'shape.border.color': lastColor,
                    'all.color': lastColor,
                    "all.transparent": true,
                    'all.opacity': 0.3,
                });
                p3 = [nodePoints[0]['x'], tall / 2, nodePoints[0]['y']];
                node.p3(p3);
                node.setTall(tall);
                node.setThickness(5);
                node.setPoints(nodePoints); //node設置點集位置信息
                node.setSegments(segments); //node設置點集鏈接規則
                break;
        }
        g3dDm.add(node);
    }
}

考慮到電子圍欄在某些狀況下可能會影響到對人物位置的觀察,設置了隱藏電子圍欄的功能。在HT中用戶能夠自定義設置標籤Tag做爲模型惟一的標識,我將全部的電子圍欄模型的標籤前綴都統一而且保存在fenceName中,須要隱藏的時候則遍歷全部標籤名稱前綴爲fenceName的模型,而且根據模型種類的不一樣設置不一樣的隱藏方式。安全

如下是相關僞代碼:編輯器

g3dDm.each((data) => {
    if (data.getTag() && data.getTag().substring(0, 4) === fenceName) {
        if (data.s('all.opacity') === '0') {
            data.s('all.opacity', '0.3');
        }
        else {
            data.s('shape3d.visible', true);
            data.s('all.visible', true);
            data.s("2d.visible", true);
            data.s("3d.visible", true);
        }
    }
});

人物模型實時定位

由於項目使用的是http協議獲取數據,所以使用定時器定時刷新人物數據信息,HT有設置節點位置的setPosition3d方法,所以不作過多介紹,可是人物節點的位置的刷新還包括人物的朝向,所以每次人物移動都須要和上次位置進行比對,計算出偏移的角度。工具

相關僞代碼以下:

// 刷新數據的人物結點與原來的人物節點標籤相同,則存在作位置更新
if (realInfoData.tagId === tag.getTag()) {
    //計算位置朝向偏移參數
    let angleNumber = Math.atan2(((-p3[2]) - (-tag.p3()[2])), ((-p3[0]) - (-tag.p3()[0])));
    //若是在原地就不轉向,判斷人物在平面位置是否發生變化
    if (p3[0] !== tag.p3()[0] || p3[2] !== tag.p3()[2]) {
        if (angleNumber > 0) {
            angleNumber = Math.PI - angleNumber;
        } else {
            angleNumber = -Math.PI - angleNumber;
        }
        //設置人物朝向
        tag.setRotation3d(0, angleNumber + Math.PI / 2, 0);
    }
    //設置人物位置
    tag.p3(p3);
}

人物觸發警報

當人物觸發警報時,有2種方式同時提醒系統使用者。一是人物頭上的面板顏色發生改變,而且顯示報警信息。

相關代碼以下:

switch(obj.alarmType){
    case null:
        if(panel){//無警報
            panel.a('alarmContent','');
            panel.a('bg','rgba(6,13,36,0.80)');
        }
        break;
    case '0':
        panel.a('alarmContent','進入圍欄');
        panel.a('bg','rgb(212,0,0)');
        break;
    case '1':
        panel.a('alarmContent','SOS');
        panel.a('bg','rgb(212,0,0)');
        break;
    case '2':
        panel.a('alarmContent',''); //離開圍欄
        panel.a('bg','rgba(6,13,36,0.80)');   
        break;
    case '3':
        panel.a('alarmContent','長時間未動');
        panel.a('bg','rgb(212,0,0)');
        break;
}

二是頁面的右側面板會增長警報信息。

相關代碼以下:

data.a('text', info);
list.dm().add(data);

軌跡圖軌跡實現原理

在發生警報後,須要根據人物的軌跡圖回溯發生警報的前因後果。若是使用根據點集每走一步就繪製一個canvas腳步節點的方式去重現軌跡,很容易形成節點繪製過多,頁面卡頓的狀況,所以我使用一整條管道的方式代替一我的物的全部腳步節點,使用管道的好處是,每一個人物的軌跡圖從開始到結束只有一個管道的圖元信息,所以對頁面的渲染更加友好和流暢。

生成管道軌跡的代碼以下:

//生成軌跡
this.ployLines[i] = new ht.Polyline();
this.ployLines[i].setParent(node);
this.points[i] = [];
this.points[i].push({ x: p3[0], y: p3[2], e: p3[1] -50 });
this.ployLines[i].setPoints(this.points[i]);
this.ployLines[i].s({
    'shape.border.color': 'red'
});
g3dDm.add(this.ployLines[i]);

人物前進一步,則往管道的點集中推動一個點的座標,同時繪製新的管道部分。同理,人物後退一步,則管道的點集中推出當前最後一個點的座標,同時管道失去最後兩點鏈接的部分。另外我經過使用定時器,對軌跡圖的前進和後退分別作了快進和快退的處理。如下爲軌跡圖的運行效果:

相關文章
相關標籤/搜索