基於 WebGL 的 HTML5 3D 工控隧道可視化系統

前言

隧道的項目我目前是第一次接觸,感受作起來的效果還蠻讚的,因此給你們分享一下。這個隧道項目的主要內容包括:照明、風機、車道指示燈、交通訊號燈、情報板、消防、火災報警、車行橫洞、風向儀、COVI、微波車檢以及隧道緊急逃生出口的控制。這個例子我主要講一下風向儀、排風以及逃生出口的動畫設置, 還有就是點擊交通訊號燈,彈出窗口能夠選擇當前信號燈的信息等內容。javascript

本文例子演示地址:www.hightopo.com/demo/tunnel…html

本例效果圖:java

圖片描述

項目實現

我是用 HT 作的整個例子,首先建立 3D 場景,HT 有 3D 組件,能夠直接經過 new ht.graph3d.Graph3dView 3D 組件來建立一個實例,而後經過 getView() 函數獲取組件的底層 div,既然是 div,那位置顯示控制就容易得多了:json

dm = new ht.DataModel(); // 數據容器,能夠將顯示在界面上的全部數據經過 dataModel.add 存儲在數據容器中
g3d = new ht.graph3d.Graph3dView(dm); // 3D 組件
g3d.addToDOM(); // 將 3D 組件的底層 div 添加到 body 中
複製代碼

HT 的組件通常都會嵌入 BorderPane、SplitView 和 TabView 等容器中使用,而最外層的 HT 組件則須要用戶手工將 getView() 返回的底層 div元素添加到頁面的 DOM 元素中,這裏須要注意的是,當父容器大小變化時,若是父容器是 BorderPane 和 SplitView 等這些HT預約義的容器組件,則HT的容器會自動遞歸調用孩子組件 invalidate 函數通知更新。但若是父容器是原生的 html 元素, 則HT組件沒法獲知須要更新,所以最外層的 HT 組件通常須要監聽 window 的窗口大小變化事件,調用最外層組件 invalidate 函數進行更新。數組

爲了最外層組件加載填充滿窗口的方便性,HT 的全部組件都有 addToDOM 函數,其實現邏輯以下,其中 iv 是 invalidate 的簡寫:app

addToDOM = function(){   
    var self = this,
        view = self.getView(),  // 獲取組件的底層 div
        style = view.style;
    document.body.appendChild(view); // 將組件的底層 div 添加進 body 中 
    style.left = '0'; // HT 組件默認設置 position 樣式屬性爲 absolute 絕對定位方式
    style.right = '0';
    style.top = '0';
    style.bottom = '0';      
    window.addEventListener('resize', function () { self.iv(); }, false);            
}
複製代碼

加載場景

最讓我開心的應該是個人開發基本上跟設計部分徹底分離了,由於 HT 能夠經過 ht.Default.xhrLoad 函數直接加載 json 文件的場景,這樣我跟設計師就是雙進程了,很是開心呢~加載場景有三個步驟,以下:函數

ht.Default.xhrLoad('scenes/隧道.json', function(text){ // 加載 json 場景
    var json = ht.Default.parse(text); // 轉義 json 文件
    dm.deserialize(json); // 將 json 內容反序列化到場景中
    // 能夠在這個裏面任意操做 datamodel 數據容器中的數據了
}
複製代碼

添加動畫

我在場景中添加了一些功能,包括前面提到過的一些動畫操做,HT 封裝好的 dataModel.addScheduleTask(task) 經過操做數據容器 dataModel 來控制加載動畫,動畫部分在參數 task 中聲明,task 爲 json 對象,可指定以下屬性:字體

  • interval:間隔毫秒數,默認值爲 10
  • enabled:是否啓用開關,默認爲 true
  • action:間隔動做函數,該函數必須設置

個人動畫一共三個,兩個隧道中各有一個風扇、一個風向儀以及一個卷閘門。設置這三個圖元變化便可,我在 json 中分別將這三個圖元的 tag 設置爲 feng、feng2 以及 door,在代碼中我就能夠直接調用這三個圖元的 tag 屬性:動畫

var task = {
    action: function(data){
        if(!data.getTag()) return;
        var tag = data.getTag(); // 獲取圖元的 tag 屬性
    if(tag === 'feng'){
        data.r3(0, (data.r3()[1]+Math.PI/12), 0); // r3 爲 3d 中的旋轉,這裏 y 軸在原來的基礎上再旋轉 Math.PI/12 角度
    }else if(tag === 'feng2'){
        data.r3(0, 0, data.r3()[2]+Math.PI/12);
    }else if(tag === 'door'){
            if(data.getTall() > 0){ // 獲取圖元的 tall 屬性,高度
                data.setTall(data.getTall()-20); // 設置高度爲當前高度減去20
            }
        }
    }
}
dm.addScheduleTask(task); // 在數據容器 dataModel 中添加調度任務
複製代碼

添加信息表單

接着是建立 form 表單,在表單上添加一些信息,好比交通燈的切換等等,場景默認顯示的右上角的 form 表單咱們這裏不作解釋,內容跟點擊交通燈出現的 form 表單差很少,因此咱們主要說明一下點擊交通燈時出現的表單:ui

圖片描述

表單中重複的部分比較多,我挑出三個部分來解釋一下:文本部分、「當前狀態」顯示的圖標以及下面「修改狀態」中的圖標點擊選擇部分:

form.addRow([ // addRow 添加一行 我這個部分是添加一個標題
    {
        element: '交通燈控制', // 這一行第一部分的顯示文本
        align: 'center', // 文本對齊方式
        color: 'rgb(0,210,187)', // 文本顏色
        font: 'bold 16px arial, sans-serif' // 文本字體
    }
], [0.1]); // 記得要設置這行的寬度
form.addRow([ // 這行中有兩個部分,一個「設備描述」,一個 文本「0」,因此要設置兩個寬度,寬度要放在一個數組中 
    '設備描述:', // 第一部分
    { // 第二部分
        element: '0',
        color: 'rgb(0,210,187)'
    }
],[80, 0.1], 34); // addRow 函數第二個參數爲寬度設置,將上面內容的寬度依次放進這個數組中。第三個參數爲高度
form.addRow([     
    '當前狀態:',
    { // 也能夠將數組中的某個部分設置爲空字符串,佔據一些寬度,這樣比例比較好調
        element: ''
    },
    {
        id: '105', // id惟一標示屬性,可經過formPane.getItemById(id)獲取添加到對應的item對象
        button: { // 按鈕,設置了該屬性後HT將根據屬性值自動構建ht.widget.Button對象,並保存在element屬性上
            icon: 'symbols/隧道用圖標/light.json', // 按鈕上的顯示圖標
            background: 'rgba(0,7,26,0.60)', // 按鈕背景
            borderColor: 'rgb(0, 7, 26)', // 按鈕邊框顏色
            clickable: false // 是否可點擊
        }
    }
],[80, 0.1, 84], 30);
form.addRow([ // 若是和上面一行的距離差異與其它行間距不一樣,能夠經過增長一行空行,設置高度便可 
    '',
    {
        element: ''
    }
], [200, 0.1], 10);
form.addRow([                    
    '修改狀態:',
    {
        element: ''
    },
    {
        button: {
            icon: 'symbols/隧道用圖標/light.json', // 設置按鈕的圖標
            background: 'rgba(0,7,26,0.60)',
            borderColor: 'rgb(0, 7, 26)',
            groupId: 'btn', // 經過getGroupId和setGroupId獲取和設置組編號,屬於同組的togglable按鈕具備互斥功能。後面的三個按鈕也是設置了同一個 groupId
            onClicked: function(e){ // 點擊後的回調函數
                btnClick('light'); 
            }
        }
    }
],[80, 0.1, 84], 30);
複製代碼

這個 form 表單的背景只是設置了一張圖片而已:

background: url('assets/控制.png') no-repeat;
複製代碼

上面還有一個部分沒有說起,就是點擊按鈕後調用的 btnClick 函數:

function btnClick(imageName){
    if(flag === 1){ // 作的判斷是根據3d的事件來處理的,等下會提
        dm.getDataByTag('light').s({ // 經過getDataByTag獲取節點,設置節點的style樣式
            'back.image': 'symbols/隧道用圖標/'+imageName+'.json', // 設置圖元的背面圖片
            'front.image': 'symbols/隧道用圖標/'+imageName+'.json' // 設置圖元你的前面圖片
        });
    }else if(flag === 2){
        dm.getDataByTag('light1').s({
            'back.image': 'symbols/隧道用圖標/'+imageName+'.json',
            'front.image': 'symbols/隧道用圖標/'+imageName+'.json'
        });
    }else{}
    form.getViewById(105).setIcon('symbols/隧道用圖標/'+imageName+'.json'); // 設置id爲105的item內容顯示的圖標爲form表單上點擊的交通燈的按鈕的圖標
}
複製代碼

點擊事件

最後就是點擊事件了,一個是點擊 3D 中的交通燈後出現交通燈控制的 form 表單,還有一個就是點擊 form 表單上的「修改狀態」中的圖標事件:

g3d.mi(function(e){ // addInteractorListener 函數 監聽場景中的事件
    form.getView().style.display = 'none'; // 全部的點擊圖元事件都先將form隱藏
    if(e.kind === 'clickData'){ // 點擊圖元
        if(e.data.getTag() === 'light'){ // 若是圖元是正面的隧道的燈
        	form.getView().style.display = 'block'; // 顯示form表單
            form.iv(); // 刷新form表單,否則界面沒法獲知此時須要刷新form顯示上面的內容,必須手動操做
        	flag = 1;
        }else if(e.data.getTag() === 'light1'){ // 背面的隧道的燈,點擊切換隧道中的燈的顯示,另一個隧道中的燈不可能一塊兒改變,因此要區分開
        	form.getView().style.display = 'block';
            form.iv();
        	flag = 2;
    }else if(e.data.getTag() === 'wall'){ // 隧道牆體
        e.data.s({
            'shape3d.transparent': true, // 打開設置透明的開關
        	'shape3d.opacity': 0.2, // 設置透明度
        	'3d.selectable': false // 點擊後就不可選中隧道了
        });
    }
    }else if(e.kind === 'doubleClickBackground'){ // 點擊空白處 設置隧道牆體不透明
        dm.getDatas().forEach(function(data){ // 遍歷dataModel
        if(data.getTag() === 'wall'){
            data.s({
            	'shape3d.transparent': false, // 關閉設置透明的開關,這樣能夠不用控制透明度
            	'3d.selectable': true // 雙擊空白處可使得隧道變得可選中
        });
        }
    });
    }
});
複製代碼

本文例子地址:www.hightopo.com/demo/tunnel…

相關文章
相關標籤/搜索