1. 程式人生 > >基於 HTML5 WebGL 的樓宇智慧化整合系統(一)

基於 HTML5 WebGL 的樓宇智慧化整合系統(一)

前言       隨著現代通訊技術、計算機技術、控制技術的飛速發展,智慧建築已經成為現代建築發展的主流。智慧建築是以建築物為平臺,兼備資訊設施系統、資訊化應用系統、建築裝置管理系統、公共安全系統等。集結構、系統、服務、管理及其優化組合為一體,向人們提供一個安全、高效、便攜、節能、環保、健康的建築環境。         IBMS(Intelligent Building Management System),即智慧化整合系統,是指在 BAS 的基礎上更進一步的與通訊網路系統、資訊網路系統實現更高一層的建築整合管理系統。在資訊化時代的今天,諸多建築與整合的管理系統融合,生成了一套高效的智慧建築整合解決方案。IBMS 更多突出的是管理方面的功能,即如何的全面實現優化控制和管理,節能降耗、高效、舒適、環境安全這樣一個目的,可以這樣說,判斷一個建築物是否具有智慧建築特點,要看它是否具有 IBMS 的系統整合。這是很重要的判定條件。另一個重要的前提是,在做好這項工程的同時不要忽視了同步建設的資訊化工程。一個成功的 IBMS 系統整合會在諸多的管理方面能發揮其顯著的經濟優勢。         傳統的 智慧樓宇/樓宇自動化/樓宇安防/智慧園區 常會採用 BIM(建築資訊模型 Building information modeling)軟體,如 Autodesk 的 Revit 或 Bentley 這類建築和工程軟體,但這些 BIM 建模模型的資料往往過於龐大臃腫,絕大部分細節資訊對樓宇自控意義不大,反而影響拖累了行業 Web SCADA 或 Web 組態監控的趨勢,所以我們採用以 Hightopo 的 HT for Web 產品輕量化 HTML5/WebGL 建模的方案,實現快速建模、執行時輕量化到甚至手機終端瀏覽器即可 3D 視覺化運維的良好效果。         本系列文章為了幫助使用者更直觀友好的瀏覽當前的樓宇智控系統,分成了三個小節來介紹場景以及效果實現的運用: 1、冷站,熱站,中央空調末端智慧群控系統 以及 3D 動畫效果以及切換漫遊; 2、面板元件動畫效果和 樓層監控系統 視訊的引入; 3、智慧樓宇管理系統、電梯監控系統 以及 停車場管理系統;   介面簡介及效果預覽 介面初始化及漫遊效果       場景在載入的時候會讀取模型資訊,確認模型載入完畢後才開始執行動畫效果,然後通過場景的漫遊效果,可以直觀地去巡視整座大樓建築的場景資訊。     冷站場景效果:優化冷水機組效率,按需供給       在智慧樓宇的中央空調冷熱源系統中,冷站場景分有冷卻水系統、冷水機組以及冷凍水系統所組成。冷卻水系統的作用是為冷水機組的冷凝器提供冷卻水,吸收製冷劑的冷凝熱量,並將冷凝熱量轉移到大氣中去。冷凍水系統的作用是為冷水機組的蒸發器提供的冷量通過冷凍水輸送到各類冷水使用者。     熱站場景效果:優化熱泵機組效率,按需供給       在智慧樓宇的中央空調冷熱源系統中,熱站場景分有冷卻機組系統和熱水泵系統所組成。通過冷卻機組系統的換熱器不斷加熱了中央空調系統內的空調水,並通過熱水迴圈中的熱水泵系統進行迴圈給使用者提供熱量。   中央空調末端智慧群控系統場景效果:靈活對應多樣智慧調節空間       在智慧樓宇的中央空調冷熱源系統中,末端智慧節能控制系統,通過室內溫溼度可以進行模組內部的調節送風溫度,水閥開度及風機頻率,在保證末端舒適度的前提下,使供冷量與需求相匹配,最大限度地降低風機能耗。   智慧樓宇管理系統優化效果       主要包括冷站、熱站和中央末端智慧群控的聯合作用,以及樓層智慧照明,通過清晰的動畫體現出整棟大樓智慧節能運作的流程,可以通過面板詳情的演示細緻地介紹每個場景的作用以及串聯的用處。   電梯以及樓層監控效果       視覺化地實時監控電梯在樓層間的工作執行狀態,並且能夠準確地瀏覽每個電梯內的實時監控畫面。     停車場管理系統監控效果       停車場作為現在樓宇監控不可缺失的一環,這裡主要可以體現出實時的車位監控,通過簡單的動畫演示來表現出整個停車場車輛的執行狀態,方便管理。   程式碼實現 一、場景搭建       介面通過 2D 圖紙疊加在3D 場景上來實現 2D 介面 與 3D 場景的融合,2D 介面通過自動佈局的機制實現了手機端與電腦端的響應式呈現。       通過 2D 檢視的元件 ht.graph.GraphView 和 3D 檢視的元件 ht.graph3d.Graph3dView 創建出呈現 2D 檢視的元件類 g2d 以及呈現 3D 檢視的元件類 g3d,在分別獲取各自的資料模型 DataModel,來對圖紙場景做一些資料視覺化的操作,這裡值得一提的是,我對於 2D 介面和 3D 場景的融合,是通過把 getView() 獲取到 g3d 拓撲元件的根層 div,然後 addToDOM() 將 g2d 元件加入到指定的 DOM 元素底下。       可以通過<HT的入門手冊>瞭解到更多檢視與資料模型之間的內容。
// 建立二維拓撲檢視
this.g2d = new ht.graph.GraphView();
this.g2dDm = this.g2d.dm();

// 建立三維拓撲檢視
this.g3d = new ht.graph3d.Graph3dView();
this.g3dDm = this.g3d.dm();

// 將二維圖紙嵌入到三維場景中
this.g2d.addToDOM(this.g3d.getView());

// 修改左右鍵互動方式
let mapInteractor = new ht.graph3d.MapInteractor(this.g3d);
this.g3d.setInteractors([mapInteractor]);

// 修改最大仰角為 PI / 2
mapInteractor.maxPhi = Math.PI / 2;

const G = {};
window.G = G;
// 事件派發
G.event = new ht.Notifier();

 

3D 場景載入主檢視為:

 

      首先我搭建了一個 3D 的場景用來放置我們的 json 場景資料,利用 ht.Default.xhrLoad 函式解析 json 場景資料,並通過 deserialize 將反序列化的物件加入DataModel來顯示載入 3D 場景,有興趣的可以通過<HT的序列化手冊>來了解這一機制的實現。

ht.Default.xhrLoad('scenes/demo.json', (json) => {
    if (!json) return;
    g3dDm.deserialize(json);

    // 設定三維檢視的中心點和相機位置
    g3d.setCenter([-342, -64, 389]);
    g3d.setEye([-355, 10833, 2642]);

    // 設定最遠距離
    g3d.setFar(1000000);
    // 獲取球圖示,設定為天空球
    let skybox = g3dDm.getDataByTag('skyBox');
    g3d.setSkyBox(skybox);

    // 模型載入完後執行動畫
    const modelList = [];
    g3dDm.each(d => {
        const shape3d = d.s('shape3d');
        if (!shape3d || !shape3d.endsWith('.json')) return;
        if (ht.Default.getShape3dModel(shape3d)) return;
        modelList.push(shape3d);
    });
    ht.Default.handleModelLoaded = (name, model) => {
        const index = modelList.indexOf(name);
        if (index < 0) return;
        modelList.splice(index, 1);
        if (modelList.length > 91) return;
        ht.Default.handleModelLoaded = () => {
        };

        // 模型載入完侯,預設執行場景切換動畫
        g3d.moveCamera([257, 713, 1485], [7, 40, 144], {
            duration: 2000,
            finishFunc: () => {
                this.load2D();
            }
        });
    };
});

 

2D 面板載入檢視為:

 

      同樣,我搭建了一個 2D 的場景用來放置我們的 json 向量圖,利用 ht.Default.xhrLoad 函式將 json 向量背景圖反序列化顯示在 2D 面板資料。

ht.Default.xhrLoad('displays/demo.json', (json) => {
    if (!json) return;
    g2dDm.deserialize(json);

    // 面板動畫入口
    this.tittleAnim();
    this.panelTime();

    // 2D圖紙載入完後執行事件處理
    this.loaded2DHandler();
});

 

二、3D 動畫效果以及切換漫遊

      對於 3D 建模下的樓宇建築,加上場景的全方位漫遊,可使使用者達到一種沉浸式的體驗,更加直觀地去感受這個樓宇下各個場景的聯絡,依次地介紹了冷站、智慧末端以及熱站的位置以及功能運作的動畫 。主要運用的方法是通過藉助 HT 提供的 ht.Shape 圖元型別,可以在 GraphView 和 Graph3dView 元件上展示出各種二維和三維的形狀效果,而漫遊的管道路線就是由其擴充套件子類 ht.Polyline 去繪製實現一條三維的管道,然後用這條繪製的管道加上漫遊的時間去呼叫這個漫遊的方法,其本質上是圍繞著中心點,然後根據管道去不斷地改變視角下的 eye 和 center 的數值,達到環視這個建築的整體視角。

      這裡可以瞭解一下關於空間軌道的繪製,詳見<HT的形狀手冊>的空間管線章節。

      以下是環視漫遊動畫的虛擬碼:

polyLineRoam(polyLine, time) {
    const g3d = this.g3d;
    const g3dDm = this.g3dDm;
    this.roamButton.a('active', true);
    this.roamAnim = ht.Default.startAnim({
        duration: time,
        easing: t => t,
        action: (v, t) => {
            let length = this.main.g3d.getLineLength(polyLine),
            offset = this.main.g3d.getLineOffset(polyLine, length * v),
            point = offset.point,
            px = point.x,
            py = point.y,
            pz = point.z;

            g3d.setEye(px, py, pz);
            g3d.setCenter(7, 40, 144);
        },
        finishFunc: () => {
            this.roam1();
        }
    });
}

 

      在整體建築的環視漫遊完後,我們可以通過拉近各個場景的視角,來依次巡視各個場景所執行的動畫。在根據管道改變 eye 和 center 環視漫遊方法結束後,用動畫的結束回撥 finishFunc 去呼叫下一個動畫的執行,而巡視漫遊就在這裡去呼叫,以下我們以巡視冷站的漫遊動畫為例去介紹實現的方法。

      巡視漫遊的主要實現方法是通過 HT 核心包的相機移動 moveCamera 來實現的, 通過引數 (eye, center, animation) 來呼叫這個方法:

  • eye:新的相機位置,形如[-291, -8, 283],如果為 null 則使用當前相機的位置;
  • center:新的目標中心點位置(相機看向的位置),形如[148, -400, 171],如果為 null 則使用當前中心點位置;
  • animation:預設 false,是否啟用動畫,可以設定為 true 或者 flase 或者 animation 動畫物件;

      每次執行完一個場景的視角移動後,再通過相機移動動畫的結束回撥 finishFunc 呼叫下一個相機移動的動畫,達到巡視漫遊的效果。

// 切換到冷站視角
roam1() {
    const g3d = this.g3d;
    const g3dDm = this.g3dDm;
    this.roamAnim = g3d.moveCamera([-291, -8, 283], [148, -400, 171], {
        duration: 500,
        easing: t => t * t,
        finishFunc: () => {
            this.roam2();
        }
    });
}

 

      在環視漫遊和巡視漫遊的執行下,我們也可以觸發 2D 圖紙右面板下的按鈕面板去觀看我們想要瀏覽的指定場景,這時候就會關閉當前在執行的環視漫遊或者巡視漫遊,再次點選改按鈕則返回場景的主視角,或者點選左上角漫遊按鈕又可以進入環視漫遊,這樣的互動體驗,可以方便使用者即使地檢視想要瀏覽的場景,而不用依靠等待逐一漫遊下去檢視,也不會干擾到漫遊的整體體驗。相應地通過介紹冷站按鈕的點選觸發介紹一下實現的方法。

      一般的互動方式存在三種事件互動的方法,包括事件通知管理器 ht.Notifier 類,內建的 Interator 在互動過程會派發出事件和資料繫結的監聽來實現,而這裡使用的是第三種互動方式。

      通過資料繫結監聽到 onDown 執行按下的事件後,通過改變按下和再次按下的按鈕狀態 active 來分別執行相機移動去切換視角,主要實現的虛擬碼如下:

// 設定圖元可互動
this.coolingCentralStationButton.s('interactive', true);
// 通過資料繫結監聽到onDown執行按下的事件
this.coolingCentralStationButton.s('onDown', () => {
// 切換到冷站時,2d面板所執行的切換動畫
this.switchToColdStation();
// 按鈕初始化
this.buttonTearDown();
// 按鈕按下效果的狀態
    let active = this.coolingCentralStationButton.a('active');
    // button為按鈕集合陣列,當按下電梯按鈕,其他按鈕預設false
    button.forEach(btn => {
        btn.a('active', false);
    });
    // 冷站按鈕的狀態切換
    this.coolingCentralStationButton.a('active', !active);
    // 根據冷站按鈕的狀態執行切換到冷站或者切換回主視角
    if (active) {
        // 相機移動切換到主視角
        moveCamera(g3d, [257, 713, 1485], [7, 40, 144], {
            duration: 2000,
            easing: t => t * t
        });
    } else {
        // 漫遊動畫物件如果不為空,則暫停漫遊動畫物件並且設定為空
        if (this.roamAnim !== null) {
            this.roamAnim.pause();
            this.roamAnim = null;
        }
        // 相機移動切換到冷站視角
        coolingCentralStationAnimation = moveCamera(g3d, [-291, -8, 283], [148, -400, 171], {
            duration: 2000,
            easing: t => t * t
        });
    }
});

 

      當然,在 3D 場景下還有一些很有趣的動畫效果,比如車流效果、飛光效果和圓環擴散效果。車流效果主要通過採用了貼圖的 uv 的偏移來實現達到車流穿梭的科技感效果;而飛光效果則是採用排程動畫的方法來間隔設定飛光的高度,達到最高點則消失然後重新輪迴動畫展示;圓環擴散效果則是同樣採用排程動畫的方法來間隔設定圓環的縮放值和透明度,來達到擴散消失的效果。

      對於間隔的排程動畫,為了實現動畫的流暢性,這裡排程使用的 loop 是運用到自己封裝 HT 的動畫 ht.Default.startAnim 的一個方法:

  • frames 動畫幀數,這裡不鎖定幀數,可以適應本身動畫的幀數;
  • interval 動畫間隔,單位ms,預設設定20ms。
loop(action, interval = 20) {
    return ht.Default.startAnim({
        frames: Infinity,
        interval: interval,
        action: action
    });
}

 

      然後通過呼叫這個 loop 的間隔動畫方法,我們來實現車流效果、飛光效果和圓環擴散效果,實現的參考虛擬碼如下:

// 車流圖元的初始化
let traffic = g3dDm.getDataByTag('traffic');
// 圓環擴散圖元的初始化
let lightRing = this.lightRing = g3dDm.getDataByTag('lightRing');
// 飛光圖元設定三種透明狀態陣列集合flyMap的初始化
[1, 2, 3].forEach(i => {
    const data = flyMap['fly' + i] = g3dDm.getDataByTag('fly' + i);
    data.eachChild(d => {
        d.s({
            // 開啟透明度
            'shape3d.transparent': true,
            // 根據不同的陣列集合設定不同的透明度
            'shape3d.opacity': i === 3 ? 0.5 : 0.7,
            // 設定沿著y軸自動旋轉
            'shape3d.autorotate': 'y'
        });
    });
});

if (this.flyAnim) return;
this.flyAnim = loop(() => {
    // 飛光根據間隔設定高度來達到上升的效果
    for (let k in flyMap) {
        const data = flyMap[k];
        let e = data.getElevation() + flyDltMap[k];
        if (e >= 500) e = -400;
        data.setElevation(e);
    }

    // 車流根據設定間隔增長uv偏移量來實現穿梭的效果
    traffic.eachChild(c => {
        c.s('all.uv.offset', [location, 0]);
    });
    location -= 0.03;

    // 旋轉震盪波透明度漸降
    let percent = lightRing.a('percent') || 0,
        scale = 15 * percent + 0.5;
    lightRing.setScale3d([scale + 1, scale, scale + 1]);
    lightRing.s('shape3d.opacity', (1 - percent) * 0.5);
    percent += 0.01;
    if (percent >= 1) {
        percent = 0;
    }
    lightRing.a('percent', percent);
}, 50);

 

三、冷站場景和熱站場景的動畫實現

      場景動畫中機組的風扇、集水器的蓄滿以及水的流動效果:

      動畫的實現主要還是通過 HT 自帶的 ht.Default.startAnim 動畫函式,支援 Frame-Based 和 Time-Based 兩種方式的動畫。同樣的,我們這裡使用的是 Frame-Based 來封裝一個 loop 函式來執行每一幀間隔的動畫。

      一般來說,動畫可通過自行配置來達到自己想要實現的方法,這裡可以瞭解< HT 的入門手冊>關於動畫函式的介紹。

if (this.stationAnim) return;
this.stationAnim = loop(() => {
    // 冷站水管流動
    coldFlow_blue.eachChild(c => {
        c.s('shape3d.uv.offset', [-location, 0]);
    });
    coldFlow_yellow.eachChild(c => {
        c.s('shape3d.uv.offset', [location, 0]);
    });

    // 熱站水管流動
    heatFlow_blue.eachChild(c => {
        c.s('shape3d.uv.offset', [-location, 0]);
    });
    heatFlow_yellow.eachChild(c => {
        c.s('shape3d.uv.offset', [location, 0]);
    });

    location -= 0.03;

    // 冷站風扇旋轉
    cold_fan.eachChild(c => {
        c.setRotation3d(c.r3()[0], c.r3()[1] + (Math.PI / 10), c.r3()[2]);
    });
    // 熱站風扇旋轉
    heat_fan.eachChild(c => {
        c.setRotation3d(c.r3()[0], c.r3()[1] + (Math.PI / 10), c.r3()[2]);
    });

    // 集水器水位變化
    HotWaterTankTall += 0.25;
    if (HotWaterTankTall > 15) {
        HotWaterTankTall = 0;
    }
    coldWaterTankTall1 += 0.25;
    if (coldWaterTankTall1 > 20) {
        coldWaterTankTall1 = 0;
    }
    coldWaterTankTall2 += 0.25;
    if (coldWaterTankTall2 > 20) {
        coldWaterTankTall2 = 0;
    }
    hotWaterTank.setTall(HotWaterTankTall);
    coldWaterTank1.setTall(coldWaterTankTall1);
    coldWaterTank2.setTall(coldWaterTankTall2);
}, 50);

 

四、中央空調末端智慧群控系統場景效果

      這裡採用了模擬資料的方式來體現末端智慧節能控制的效果。應用於真實專案的時候,可以採用資料介面的方式來實時對接真實資料,可以達到實時監控的效果。

      我使用了自己 mock 的末端群控的資料引數,格式如下:

var boxData =
    [
        [{
            // 裝置編號
            id: 'box1',
            // 裝置的溫度
            temperature: 23.8,
            // 裝置的頻率
            frequency: 45.8
        }, ...]
        ...
    ];

 

      這裡的實現也是通過 loop 迴圈執行資料的讀取,當陣列指標 index 讀取到最後一個數據時,立即關閉迴圈並清空 loop排程。

boxAnimation = loop(() => {
        for (let i = 0, l = 16; i <= l-1; i++) {
            let roomTag, roomBox, tag;
            tag = i+1;
            roomTag = 'boxPanel' + tag;
            roomBox = 'box' + tag;

            let panel = g3dDm.getDataByTag(roomTag);
            let box = g3dDm.getDataByTag(roomBox);
            if (panel) {
                panel.a('valueT', boxData[index][i].temperature + '℃');
                panel.a('valueK', boxData[index][i].frequency + 'Hz');
                // 手動更新快取的面板資訊
                g3d.invalidateShape3dCachedImage(panel);
                // 根據溫度判斷裝置的顏色
                if (box && parseFloat(panel.a('valueT')) < 26) {
                    box.s('shape3d.blend', 'rgb(4,67,176)');
                    box.s('wf.color', 'rgb(4,67,176)');
                } else if (box && parseFloat(panel.a('valueT')) >= 26 && parseFloat(panel.a('valueT')) <= 28) {
                    box.s('shape3d.blend', 'rgb(28,189,87)');
                    box.s('wf.color', 'rgb(28,189,87)');
                } else if (box && parseFloat(panel.a('valueT')) > 28) {
                    box.s('shape3d.blend', 'rgb(181,43,43)');
                    box.s('wf.color', 'rgb(181,43,43)');
                }
            }
        }
        index++;
        if (index >= 10) {
            boxAnimation.pause();
            boxAnimation = null;
        }
    }, 500);

 

總結

      IBMS 智慧化整合系統管理對於建築園區管理的重要性日趨上升,在資訊時代裡不僅可以很好地體現出資訊資料管理的明確性,也體現了智慧管理的便利有效性。通過 3D 場景樓宇園區的動畫加上環視漫遊和巡視漫遊的配合,充分體現了 3D 場景的擬真優點,但是如何實現場景動畫的觸發實現呢?這裡當然必不可少了 2D 面板上的互動和動畫,在下期我們會為大家介紹一些 2D 面板的互動和動畫實現,帶您解讀不一樣的 2D/3D 融合。         2019 我們也更新了數百個工業網際網路 2D/3D 視覺化案例集,在這裡你能發現許多新奇的例項,也能發掘出不一樣的工業網際網路:https://mp.weixin.qq.com/s/ZbhB6LO2kBRPrRIfHlKGQA       同時,你也可以檢視更多案例及效果:https://www.hightopo.com/demos/index.html