// 控制是否载入 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);
}
}
// 遍历数据模型获取所要寻找的标识节点做相应的动画
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);
}
});
}
- 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);
})
}