1. 程式人生 > >基於 HTML WebGL 的會展中心智慧監控系統

基於 HTML WebGL 的會展中心智慧監控系統

## **前言** 隨著近幾年物聯網、萬物互聯等諸多概念的大行其道,智慧城市的概念也早已經被人們耳熟能詳,而作為城市的組成部分,**智慧建築**也是重中之重,智慧園區,智慧小區等也如雨後春筍般的相繼出現。 智慧建築是指通過將建築物的結構、系統、服務和管理根據使用者的需求進行最優化組合,從而為使用者提供一個高效、舒適、便利的人性化建築環境,智慧建築絕不僅僅只是智慧園區、智慧小區這種模式,這裡我就通過 **HT for Web** 製作了一個以**會展中心**為主體的智慧建築監控系統。 ## **效果預覽** ![](https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200421150139363-345431036.png) ![](https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200421150159057-980567122.png) ![](https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200421150215967-1063111252.png) ## **程式碼實現** ### **場景呈現** 通過上面的效果預覽,可以分辨出整個監控系統是分為 3 個層次的,分別是主體、樓內、展廳,如果是使用單個 `graph3dView` 載入所有場景,通過 `dm.clear()` 清除場景,`dm.deserieialize()` 載入新場景這種切換方式必然會有一個極短的渲染時間,使切換時不連貫,所以我這裡就使用了 3 個 `graph3dView` ,去呈現各自的層級模型,通過 `notifier` 事件通知器監聽場景切換,程式碼如下: ``` javascript notifier.add((event) => { if (event.kind === 'sceneChange') { const oldSceneKey = event.oldScene, newSceneKey = event.newScene, oldScene = G[oldSceneKey], newScene = G[newSceneKey]; oldScene.removeFromDOM(); newScene.addToDOM(); if (newScene.graph2d.isAnimed) { newScene.graph3d.animByList(); } else { newScene.graph3d.animByList(newScene.graph2d.animByList, newScene.graph2d); } } }); ``` 其中 `removeFromDOM` 是自行封裝的一個方法 ``` javascript removeFromDOM() { const g3d = this.g3d, view = g3d.getView(); if (view.remove) { view.remove() } else { view.parentNode.removeChild(view) } this.notifier.fire({ kind: 'reset', }); } ``` 但是這樣還是有一個問題,`graph3dView` 預設如果不放到頁面中,場景中的 `obj` 等模型相關資源是不會請求和渲染的,這樣對效能是十分友好的,但是當我第一次切換場景時,還是會有短暫的請求和渲染時間,所以這裡我需要對資源進行預載入。 ### **資源預載入** 這裡我通過在 `body` 中新增一個不在視窗展示的與視窗等寬高的 `div` 元素,通過把當前不展示的 `graph3dView` 放到其中觸發對相應 `obj` 等模型資源的請求和渲染,完成預載入,程式碼如下: ``` javascript const preloadDiv = document.createElement('div'); preloadDiv.style.position = 'absolute'; preloadDiv.style.bottom = '100%'; preloadDiv.style.width = '100%'; preloadDiv.style.height = '100%'; document.body.appendChild(preloadDiv); scene2.addToDOM(preloadDiv); scene3.addToDOM(preloadDiv); ``` ### **模型載入完成後再執行動畫** ![](https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200421150254770-784559626.gif) web 頁面載入是依賴網速的,會展中心模型 `obj` 等資原始檔是有一定大小的,可能對於不同頻寬網速的使用者所需要載入的時間也不盡相同,這裡就需要判斷下 `obj` 是否全部載入完成,載入完成後再執行動畫效果,通過 `ht.Default.handleModelLoaded` 監控是否所有**模型**都請求載入完成, 載入完成後開始執行動畫,順便釋放之前預載入的 `graph3dView` ,程式碼如下: ``` javascript let modelSize = 0; ht.Default.handleModelLoaded = (name, model) => { modelSize++; if (modelSize === 62) { scene1.graph3d.enableShadow(); scene3.graph3d.enableShadow(); scene2.removeFromDOM(); scene3.removeFromDOM(); scene1.graph3d.animByList(scene1.graph2d.animByList, scene1.graph2d); } }; ``` ### **動畫依引數順序執行** ![](https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200421150417374-439953310.gif) ![](https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200421150431395-162765189.gif) ![](https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200421150447867-784271161.gif) 我想要場景第一次載入時,視角拉近後左右兩邊的面板再一點一點的加載出來,動畫效果是不完全線性順序的去執行,所以我這裡通過 `ht.Default.startAnim` 方法封裝了一套通過引數陣列進行的動畫的方法,程式碼如下: ``` javascript animByList(callback, obj) { this.isAnimed = true; const animList = this.animList, self = this; let callAnim = (ind) => { const param = animList.get(ind); param && self.anim(param, () => { callAnim(ind + 1); const lastParam = animList.get(ind + 1); lastParam || callback && callback.call(obj || this); }); }; callAnim(0); } anim(animParam, callback) { const self = this, time = animParam['time'] || 1000, easing = animParam['easing'] || function (t) { return t * t; }, func = animParam['func']; this.__animObj = ht.Default.startAnim({ duration: time || 1000, easing: easing, action: function (v, t) { const V = v, T = t; function animFunc(param) { let v = V, t = T; if (param instanceof Function) { param(v, t); } else { const type = param['type'], object = param['object'], objectTag = param['objectTag'], key = param['key'], oldValue = param['oldValue'], newValue = param['newValue'], oneTime = param['time'], scope = param['scope']; if (scope) { v = v < scope[0] ? 0 : v > scope[1] ? 1 : (v - scope[0]) / (scope[1] - scope[0]); } else { v = !oneTime || oneTime > time ? v : v * time / oneTime < 1 ? v * time / oneTime : 1; } let obj, value; obj = object ? object : objectTag ? self.view.dm().getDataByTag(objectTag) : undefined; if (!obj) return; if (!isSameType(oldValue, newValue) || !isNumORNumArray(oldValue)) return; if (oldValue instanceof Array) { if (oldValue.length !== newValue.length) return; const darr = newValue.map((n, i) => { return n - oldValue[i]; }); value = oldValue.map((n, i) => { return n + darr[i] * v; }); } else { const d = newValue - oldValue; value = oldValue + d * v; } ht.Default.setPropertyValue(obj, type, key, value); } } if (animParam instanceof Array) { animParam.forEach(ele => { animFunc(ele); }); } else { animFunc(animParam); } }, finishFunc: function () { func && func(func); callback && callback(); }, }); } ``` 引數格式如下: ``` javascript // 視角移動 param = { object: g3d, type: undefined, key: 'eye', oldValue: [-118, 5130, 15858], newValue: [-26, 1130, 3494], time: 1000, } animList.add(); // 標題從左到右出現 param = { object: title, type: 'style', key: 'clip.percentage', oldValue: 0, newValue: 1, time: 1500, }; animList.add(param); ``` ### **可點選部分高亮效果** 為了突出可以點選的部分,我加了高亮效果,設定滑鼠懸浮高亮模式,並通過 `g3d.getHighlightHelper().setFetchTargetFunc` 方式篩選需要滑鼠高亮的圖元,程式碼如下: ![](https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200421150619483-539777579.png) ``` javascript g3d.setHighlightMode('mouseover'); g3d.getHighlightHelper().setFetchTargetFunc(function (nodes) { let sortList = new ht.List(nodes); return sortList.toArray(node => { return jumpList.contains(node); }); }); ``` ### **樓層視角跳轉** 因為整體的樓層比較大,而每個樓層中可選擇的展區又比較小,所以這裡我做了一個視角調整,可以使用單獨移動視角到正視相應樓層的視角 `flyTo`,這裡除了採用右側邊欄選中移動,也做了滑鼠移入相應樓層右鍵改變視角的處理,使用了新建的類 `messageView` 做互動提示。 ![](https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200421150637570-607035870.gif) ``` javascript g3d.flyTo(floor, { animation: true, direction: [0, 1, 2], center: floor.p3().map((n, i) => { return i !==1 ? n : n + floor.getTall() / 2; }), distance: distances[newFloor - 1], }); ``` ## **總結** 隨著科技的井噴式發展,智慧建築將如雨後春筍般崛起,其應用的場景也會不斷拓展,應運而生的資料視覺化管理系統也應該配套升級,為其把數字資訊變為直觀的、以圖形影象資訊表示的資訊,清晰的展現在客戶的面前,這將是無可阻擋的時代大趨勢。 還有更多的視覺化案例可以參考:https://www.hightopo.com/demos/in