基于 HTML WebGL 的会展中心智能监控系统

前言

随着近几年物联网、万物互联等诸多概念的大行其道,智慧城市的概念也早已经被人们耳熟能详,而做为城市的组成部分,智慧建筑也是重中之重,智慧园区,智慧小区等也如雨后春笋般的相继出现。javascript

智慧建筑是指经过将建筑物的结构、系统、服务和管理根据用户的需求进行最优化组合,从而为用户提供一个高效、温馨、便利的人性化建筑环境,智慧建筑毫不仅仅只是智慧园区、智慧小区这种模式,这里我就经过 HT for Web 制做了一个以会展中心为主体的智慧建筑监控系统。html

效果预览



代码实现

场景呈现

经过上面的效果预览,能够分辨出整个监控系统是分为 3 个层次的,分别是主体、楼内、展厅,若是是使用单个 graph3dView 加载全部场景,经过 dm.clear() 清除场景,dm.deserieialize() 加载新场景这种切换方式必然会有一个极短的渲染时间,使切换时不连贯,因此我这里就使用了 3 个 graph3dView ,去呈现各自的层级模型,经过 notifier 事件通知器监听场景切换,代码以下:java

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 是自行封装的一个方法node

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 等模型相关资源是不会请求和渲染的,这样对性能是十分友好的,可是当我第一次切换场景时,仍是会有短暂的请求和渲染时间,因此这里我须要对资源进行预加载。web

资源预加载

这里我经过在 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 ,代码以下:app

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/demo...

相关文章
相关标签/搜索