• 基于 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

  • 相关阅读:
    numpy函数库中一些常用函数的记录
    python 中的tile函数,shape函数,sum函数
    数据挖掘十大经典算法(详解)
    逻辑回归
    17个新手常见Python运行时错误
    5.3.4 Hadoop序列化框架
    5.3.3 自定义writable和RawComparatorWritable
    Qt5.11.2 VS2015编译activemq发送程序 _ITERATOR_DEBUG_LEVEL错误和崩溃解决
    @ConfigurationProperties实现配置注入到实体类
    C++微信网页协议实现和应用
  • 原文地址:https://www.cnblogs.com/htdaydayup/p/12744773.html
Copyright © 2020-2023  润新知