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


前言

隨着近幾年物聯網、萬物互聯等諸多概念的大行其道,智慧城市的概念也早已經被人們耳熟能詳,而作為城市的組成部分,智慧建築也是重中之重,智慧園區,智慧小區等也如雨后春筍般的相繼出現。

智慧建築是指通過將建築物的結構、系統、服務和管理根據用戶的需求進行最優化組合,從而為用戶提供一個高效、舒適、便利的人性化建築環境,智慧建築絕不僅僅只是智慧園區、智慧小區這種模式,這里我就通過 HT for Web 制作了一個以會展中心為主體的智慧建築監控系統。

效果預覽



代碼實現

場景呈現

通過上面的效果預覽,可以分辨出整個監控系統是分為 3 個層次的,分別是主體、樓內、展廳,如果是使用單個 graph3dView 加載所有場景,通過 dm.clear() 清除場景,dm.deserieialize() 加載新場景這種切換方式必然會有一個極短的渲染時間,使切換時不連貫,所以我這里就使用了 3 個 graph3dView ,去呈現各自的層級模型,通過 notifier 事件通知器監聽場景切換,代碼如下:

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 是自行封裝的一個方法

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 等模型資源的請求和渲染,完成預加載,代碼如下:

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);

模型加載完成后再執行動畫

web 頁面加載是依賴網速的,會展中心模型 obj 等資源文件是有一定大小的,可能對於不同帶寬網速的用戶所需要加載的時間也不盡相同,這里就需要判斷下 obj 是否全部加載完成,加載完成后再執行動畫效果,通過 ht.Default.handleModelLoaded 監控是否所有模型都請求加載完成, 加載完成后開始執行動畫,順便釋放之前預加載的 graph3dView ,代碼如下:

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);
    }
};

動畫依參數順序執行



我想要場景第一次加載時,視角拉近后左右兩邊的面板再一點一點的加載出來,動畫效果是不完全線性順序的去執行,所以我這里通過 ht.Default.startAnim 方法封裝了一套通過參數數組進行的動畫的方法,代碼如下:

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();
        },
    });
}

參數格式如下:

// 視角移動
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 方式篩選需要鼠標高亮的圖元,代碼如下:

g3d.setHighlightMode('mouseover');

g3d.getHighlightHelper().setFetchTargetFunc(function (nodes) {
    let sortList = new ht.List(nodes);
    return sortList.toArray(node => {
        return jumpList.contains(node);
    });
});

樓層視角跳轉

因為整體的樓層比較大,而每個樓層中可選擇的展區又比較小,所以這里我做了一個視角調整,可以使用單獨移動視角到正視相應樓層的視角 flyTo,這里除了采用右側邊欄選中移動,也做了鼠標移入相應樓層右鍵改變視角的處理,使用了新建的類 messageView 做交互提示。

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/index.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM