4

前言

3D 可视化,就是把复杂抽象的数据信息,以合适的视觉元素及视角去呈现,方便系统的展示、维护和管理。而在可视化系统的搭建选择上,所呈现的风格样式效果多种多样,各自所突出的适用场合也不尽相同。对于科技风格上的体现, 线框模式 可能是最具有代表性意义的实现方式之一。 机房数据可视化 的管控维护的实现上,在 工业互联网 的推动下,体现的维护数据和系统搭建也越来越多样化,而 Hightopo(以下简称 HT )的 HT for Web 产品上的有着丰富的组态化可供选择,本文将介绍如何运用 HT 丰富的 2/3D 组态 搭建出一个线框楼宇可视化机房的解决方案。

界面简介及效果预览

主页面的呈现上以线框的科技风格为主的大楼设计,大楼环绕着数字飞升的光柱,场外分布着车流穿梭效果和停车场的模拟实现,其中数据中心上有着许多个设备的展示处理以及设备的预警处理,主体办公大楼侧边并列呈现着楼层的分布——办公楼层场景以及机房楼层场景;切换到内场景中的机房场景,展示了机房设备群的排列,单击选中机柜会视角转化到此设备上,并且虚化周围设备,展示出所点击的机柜信息,同时还具备一键切换查看机柜的利用率信息;另外一个内场景办公室,则起到了展示日常办公的应用场景,可以添加维护的信息数据来管理。

系统分析

纵观在工业互联网的推动下,机房可视化系统的发展是极为重要的一环。而3D可视化作为一个可进行交互式、带有多种视觉模拟体验的机房管理平台,其具有机房资产、能源、环境等管理功能,对机房全方面集中监控、统一管理。这是作为机房内部数据监控的重要一环,而整体的风格搭建上,外部建筑和设备采用的是科技风格的线框模式,在效果展示上可以更为的酷炫,交互体验上也可以增值加分。

一、线框模式的科技风格搭建

在千篇一律的场景建筑建模中,大部分都是实景上的搭建,感官上不同于科技风格的数字化展示来的震撼和酷炫,线框模式就很好地诠释了这一风格。

二、3D 可视化数据中心机房监控管理系统的搭建与应用

3D 可视化数据中心机房集中监控管理系统对机房实现远程集中监控管理,实时动态呈现设备告警信息及设备参数,快速定位出故障设备,使维护和管理从人工被动看守的方式向计算机集中控制和管理的模式转变。

代码实现

一、线框风格科技场景

在 3D 场景风格中,有着很多实现的方式选择,而线框风格是最为体现科技感的一种实现方式。场景建筑以及设备主体以线框风格展示,其中数据中心上的设备也会对应处理预警事件的展示。

线框模式是对于模型建模 obj 的一种特殊处理,机制封装在 HT 本身里面,而我们在开启线框模式的同时,可以选定一些建模风格上的限制,例如模型 obj 的四边面或者三边面就会影响到线框里线的绘制方式,这里通过合并三边面,并且开启显示四边面来起到线框风格上的简洁体现:

// 控制是否载入 obj 的四边面,否的话通过算法合并三角面
ht.Style['wf.loadQuadWireframe'] = false; 
// 控制是否显示四边面
ht.Style['wf.combineTriangle'] = true; 

在风格基调确定后,在主体大楼场景做还需要做一些简单的事件机制处理,例如模型选中状态的表现和设备预警信息弹窗的显示。

模型状态的体现是开启了模型选中的外框高亮显示:

// 开启模型选中高亮线框宽度为1
g3d.getHighlightHelper().mode = 1;

开启了模型选中高亮后,我们可以很清晰地体现出所点击的模型,搭配上点击事件的处理,设备信息弹窗的展示,在交互体验上就会有一种很友好的效果展示。对于设备信息的弹窗展示,是先通过对设备进行绑定标签,然后通过这个唯一的标签在数据模型 dataModel 去找到这个设备,然后弹出相对应的弹窗信息或者预警事件。

// 根据唯一标识标签从数据模型中获取节点信息
this.equipmentPanel = g3dDm.getDataByTag('equipmentPanel');
this.alarmEquipmentPanel = g3dDm.getDataByTag('alarmEquipmentPanel');
this.buildingPanel = g3dDm.getDataByTag('buildingPanel');

handleInteractive(e) {
    // 获取事件类型 kind 和事件处理节点 data
    const {kind, data} = e;
    if (kind === 'clickData') {
        let tag = data.getTag();
        if (!tag) return;
        if (tag === 'equipment') {
            // 获取所点击设备的位置信息
            var p3d = data.getPosition3d();
            // 设备位置信息上对应空间坐标 Y 轴上设定增加20的高度
            p3d[1] = p3d[1] + 20;
            // 获取设备面板
            var panel = this.equipmentPanel;
            // 设备面板显示展示
            panel.s('3d.visible',true);
            // 设置设备面板坐标
            panel.setPosition3d(p3d);
            // 隐藏大楼面板和预警面板
            this.buildingPanel.s('3d.visible',false);
            this.alarmPlane.s('3d.visible',false);
        }
        if (tag === 'alarmEquipment') {
            // 获取所点击设备的位置信息
            var p3d = data.getPosition3d();
            // 设备位置信息上对应空间坐标 Y 轴上设定增加20的高度
            p3d[1] = p3d[1] + 20;
            // 获取预警面板
            var panel = this.alarmEquipmentPanel;
            // 预警面板显示展示
            panel.s('3d.visible',true);
            // 设置预警面板坐标
            panel.setPosition3d(p3d);
            // 隐藏大楼面板和设备面板
            this.buildingPanel.s('3d.visible',false);
            this.equipmentPanel.s('3d.visible',false);
        }
        if(tag === 'building'){
            // 显示大楼面板
            this.equipmentPanel.s('3d.visible',true);
            // 隐藏设备面板
            this.alarmEquipmentPanel.s('3d.visible',false);
             // 隐藏预警面板
            this.buildingPanel.s('3d.visible',false);
        }
    }
    // 点击背景则隐藏所有面板信息
    if(kind === 'clickBackground'){
        this.equipmentPanel.s('3d.visible',false);
        this.alarmEquipmentPanel.s('3d.visible',false);
        this.buildingPanel.s('3d.visible',false);
    }
}

二、3D 动画效果的实现

在 3D 场景中有着许多动画的集合,可以通过简单清晰地体现出场景中一些所需的元素,例如车流穿梭、数字飞升以及停车场停放的效果,可以给静态的场景添加活力。

通过标识获取到对应的数字、车流和光柱节点,然后通过简单封装的动画函数驱动实现出以上的效果状态:

// 遍历数据模型获取所要寻找的标识节点做相应的动画
g3dDm.each((data) => {
    // 获取节点标识
    let tag = data.getTag();
    if (tag === 'num') {
        // 数字飞升动画
        animNum(data);
    } else if (tag === 'car') {
        // 设置车辆节点的初始 uv 偏移
        data.s('top.uv.offset', [1, 0]);
        // 车辆穿梭动画
        animCar(data);
    } else if (tag === 'light') {
        //光柱飞升动画
        animLight(data);
    }
});

而所有动画效果的实现,都是基于 HT 封装的 ht.Default.startAnim() 动画函数,支持 Frame-Based 和 Time-Based 两种方式的动画,本可视化系统中采取的是后面一种实现方式,通过 duration 对于动画时间的控制和 easing 让用户自定义,通过数学公式控制动画,如匀速变化,先慢后快等效果。基于动画函数的实现上,对各自展示节点的效果表现上,又封装了三个函数做对应的处理。

数字飞升动画效果实现的封装函数为:

function animNum(data) {
    // 设置节点大小的范围随机数处理
    var temp3 = 16 - 8 * (Math.random());
    // 设置动画运行时间的范围随机数处理
    var temp4 = 1200 + Math.random() * 2000;
    // 设置节点在空间坐标 Y 轴上的范围随机高度
    var temp5 = 400 + Math.random() * 200;
    // 开启动画函数
    ht.Default.startAnim({
        duration: temp4,
        easing: function (t) {
            return t * t
        },
        action: function (v, t) {
            // 获取节点的位置坐标信息
            var p3d = data.getPosition3d();
            // 设置节点的新位置坐标信息
            data.setPosition3d(p3d[0], temp5 - temp5 * v, p3d[2]);
            // 设置节点的大小信息
            data.setSize3d(temp3, temp3, temp3);
        },
        // 动画函数结束后继续回调此动画函数
        finishFunc: function () {
            animNum(data);
        }
    });
}

车辆穿梭动画效果实现的封装函数为:

function animCar(data) {
    // 开启动画函数
    ht.Default.startAnim({
        duration: 5000,
        easing: function (t) {
            return t
        },
        action: function (v, t) {
            // 判断节点的顶面贴图是否为所需的对应信息贴图
            if (data.s('top.image') === 'symbols/htdesign/填充/飞光渐变 2.png') {
                // 获取节点的 uv 偏移信息
                var offsetX = data.s('top.uv.offset')[0];
                // 设置偏移新值到节点上
                offsetX = (offsetX - 0.01) % 1;
                data.s('top.uv.offset', [offsetX, 0]);
            }
        },
        // 动画函数结束后继续回调此动画函数
        finishFunc: function () {
            animCar(data);
        }
    });
}

而光柱的实现方式上也是与数字飞升的效果一样,通过在随机的范围位置坐标内通过设定不同的时间差随机生成,来形成与数字飞升为对立面的光柱下降效果,与线框建筑的科技风格融为一体,很好地诠释了整体风格的展示,这里对于光柱的动画就不再多加赘述了。

相对应的是,停车场随机停放的效果展示,不同于以上的动画视觉展示,本身还是具有其效果意义的,可以对接真实的数据进行对整个停车场的车辆安放做可视化的数据维护和管理,而我们这里的实现上,则很好地模拟了这一事件的处理方式,也是通过一个简单的封装函数来体现停车场的动画效果:

function animPark(data) {
    // 设置随机值来体现车辆随机停放的信息
    var temp = Math.random();
    // 根据随机值判断车辆安放的状态
    if (temp<0.15) {
        data.s('all.color','rgb(255,184,77)');
    } else if (temp>0.6) {
        data.s('all.color','rgba(0,153,255,0.10)');
    }
    ht.Default.startAnim({
        duration: 2000,
        easing: function (t) {
            return t
        },
        action: function (v, t) {
        },
        // 动画函数结束后继续回调此动画函数
        finishFunc: function () {
            animPark(data);
        }
    });
}

三、机房的实现

机房的实现上,通过点击机柜转移锁定视角后展示机柜的内部信息,并且虚化周围设备的状态,双击背景则会返回到初始的视角并且恢复默认状态;其次,通过按钮触发显示机柜的利用率色块展示,可以很全面地观察到各个机柜的内部消耗信息,方便整体机房的维护和管理。

在机柜的点击事件处理上,先使得整体的机房机柜全部开启透明并且降低透明度达到整体虚化的效果,然后通过点击事件的处理,获取到节点信息,此时运用到封装好的开门动画函数,开门后虚化掉这个机柜的外壳后,显示展示机柜的内部设备信息;双击背景则恢复之前所做的效果处理操作,视角转移到默认机房视角。

在点击事件监听后,通过 HT 封装的视角转移函数 flyTo() 锁定到对应的机柜:

  • direction:默认undefined,眼睛处于目标的方向(相对目标,受到目标自身旋转影响),例如[0,1,5]在目标正面的斜向上;
  • animation:默认false,是否使用动画,可以设置为true或者false或者animation动画对象;
  • ratio:默认0.8,浮点类型,表示眼睛跟中心的距离动态计算(例如 0.8 表示眼睛在上述方向上动态计算距离以将目标包围盒的8个角全部适配到屏幕80%范围内);
g3d.flyTo(data, {
    direction: [0, 10, 10],
    animation: true,
    ratio: 0.9,
});

对于门的开启动画,首先是将门设置对应的机柜为父节点,通过点击事件的监听处理后,根据多点击的节点,将对应的门节点和旋转角度信息,去调用门的封装动画函数:

// 传入节点和旋转角度信息
export function animDoor(data, x) {
    // 开启动画函数
    ht.Default.startAnim({
        duration: 1200,
        easing: function (t) {
            return t
        },
        // 动画执行函数,根据传入的角度信息做旋转角度的动画
        action: function (v, t) {
           data.setRotation3d(0,-v * x,0);
        },
        finishFunc: function () {
            // 设置门的父节点机柜透明度为0.1
            data.getParent().s('shape3d.opacity', 0.1);
            // 遍历门的父节点机柜并设置透明度为0.1
            data.getParent().eachChild(function (data) {
                data.s('shape3d.opacity', 0.1);
            })
        }
    });
}

对于双击背景的视角返回处理,是通过 HT 封装的相机移动函数 moveCamera(),可以根据所要到达的视角中心(center)和眼睛(eye),通过开启动画函数达到一种视角切换的过渡效果:

  • eye:相机位置坐标;
  • center:中心点位置坐标;
  • anim:默认 false,是否使用动画,可以设置为true或者false或者animation动画对象;
g3d.moveCamera([1294, 898, 1671], [0, 0, 0], true);

对于机柜所占用的能耗和处理能力,可以通过机柜利用率来体现,这样不仅能直观地体现每一个机柜的使用情况,还能通过反馈的使用情况,即时对一些负载的机柜或者是低使用率的机柜,做出智能调整,使其机柜群达到最大效率化的工作状态。而具体的实现方法是通过在机柜群上动态生成,占用机柜高度比例大小的节点,通过随机取值的方式,并且约定能耗颜色的显示,来体现出机柜当前的利用率信息。

loadCapacityNode(g3dDm, cabinetList) {
    cabinetList.forEach((data) => {
        // 创建新的利用率容量节点
        var node = new ht.Node();
        // 生成随机数
        var randomNumber = Math.random() * 100;
        // 通过随机数值来体现对应的机柜利用率颜色的变化
        var color;
        if (randomNumber <= 30) {
            color = 'rgb(51,153,255)';
        } else if (randomNumber > 30 && randomNumber < 60) {
            color = 'rgb(240,225,19)';
        } else {
            color = 'rgb(242,83,75)';
        }
        // 设置利用率容量节点的位置信息
        node.p3(data.p3());
        // 设置利用率容量节点的锚点信息
        node.setAnchor3d(data.getAnchor3d());
        // 设置利用率容量节点的高度信息
        node.s3(data.getWidth(), data.getTall() * (randomNumber/100), data.getHeight());
        // 设置利用率容量节点的一些基本属性
        node.s({
            '3d.visible': false,
            '3d.movable': false,
            'all.color': color,
            'wf.visible': true,
            'wf.color': 'rgb(247,247,247)'
        });
        // 设置容量节点为机柜,方便返回房间视角的时候遍历机柜节点一并隐藏
        g3dDm.add(node);
        this.capacityList.push(node);
    })
}

总结

线框风格只是科技风格上效果展示的一种形态,HT 自身通过数年来的行业积累,总结出了许多的风格基调设定和对应的行业应用场合,加上自身丰富的组态化快速搭建上,顺应工业互联网的浪潮,可以推动实现许多行业上的解决方案,例如数字孪生的衍生,新基建的可视化系统等广泛的行业上都有涉及,在多种多样的行业领域上,相信 HT 一定带你发掘不一样的工业互联网!

2019 我们也更新了数百个工业互联网 2D/3D 可视化案例集,在这里你能发现许多新奇的实例,也能探索许多好玩的效果实现和工业互联网的展示:https://mp.weixin.qq.com/s/ZbhB6LO2kBRPrRIfHlKGQA

同时,你也可以查看更多案例及效果:https://www.hightopo.com/demos/index.html


hightopo
5.5k 声望3k 粉丝

Everything you need to create cutting-edge 2D and 3D visualization