基于 HTML5 WebGL 的楼宇智能化集成系统(一)
// 创建二维拓扑视图
this.g2d = new ht.graph.GraphView();
this.g2dDm = this.g2d.dm(); // 创建三维拓扑视图
this.g3d = new ht.graph3d.Graph3dView();
this.g3dDm = this.g3d.dm(); // 将二维图纸嵌入到三维场景中
this.g2d.addToDOM(this.g3d.getView()); // 修改左右键交互方式
let mapInteractor = new ht.graph3d.MapInteractor(this.g3d);
this.g3d.setInteractors([mapInteractor]); // 修改最大仰角为 PI / 2
mapInteractor.maxPhi = Math.PI / 2; const G = {};
window.G = G;
// 事件派发
G.event = new ht.Notifier();
3D 场景加载主视图为:
首先我搭建了一个 3D 的场景用来放置我们的 json 场景数据,利用 ht.Default.xhrLoad 函数解析 json 场景数据,并通过 deserialize 将反序列化的对象加入DataModel来显示加载 3D 场景,有兴趣的可以通过<HT的序列化手册>来了解这一机制的实现。
ht.Default.xhrLoad('scenes/demo.json', (json) => {
if (!json) return;
g3dDm.deserialize(json); // 设置三维视图的中心点和相机位置
g3d.setCenter([-342, -64, 389]);
g3d.setEye([-355, 10833, 2642]); // 设置最远距离
g3d.setFar(1000000);
// 获取球图标,设置为天空球
let skybox = g3dDm.getDataByTag('skyBox');
g3d.setSkyBox(skybox); // 模型加载完后执行动画
const modelList = [];
g3dDm.each(d => {
const shape3d = d.s('shape3d');
if (!shape3d || !shape3d.endsWith('.json')) return;
if (ht.Default.getShape3dModel(shape3d)) return;
modelList.push(shape3d);
});
ht.Default.handleModelLoaded = (name, model) => {
const index = modelList.indexOf(name);
if (index < 0) return;
modelList.splice(index, 1);
if (modelList.length > 91) return;
ht.Default.handleModelLoaded = () => {
}; // 模型加载完侯,默认执行场景切换动画
g3d.moveCamera([257, 713, 1485], [7, 40, 144], {
duration: 2000,
finishFunc: () => {
this.load2D();
}
});
};
});
2D 面板加载视图为:
同样,我搭建了一个 2D 的场景用来放置我们的 json 矢量图,利用 ht.Default.xhrLoad 函数将 json 矢量背景图反序列化显示在 2D 面板数据。
ht.Default.xhrLoad('displays/demo.json', (json) => {
if (!json) return;
g2dDm.deserialize(json); // 面板动画入口
this.tittleAnim();
this.panelTime(); // 2D图纸加载完后执行事件处理
this.loaded2DHandler();
});
二、3D 动画效果以及切换漫游
对于 3D 建模下的楼宇建筑,加上场景的全方位漫游,可使用户达到一种沉浸式的体验,更加直观地去感受这个楼宇下各个场景的联系,依次地介绍了冷站、智慧末端以及热站的位置以及功能运作的动画 。主要运用的方法是通过借助 HT 提供的 ht.Shape 图元类型,可以在 GraphView 和 Graph3dView 组件上展示出各种二维和三维的形状效果,而漫游的管道路线就是由其扩展子类 ht.Polyline 去绘制实现一条三维的管道,然后用这条绘制的管道加上漫游的时间去调用这个漫游的方法,其本质上是围绕着中心点,然后根据管道去不断地改变视角下的 eye 和 center 的数值,达到环视这个建筑的整体视角。
这里可以了解一下关于空间轨道的绘制,详见<HT的形状手册>的空间管线章节。
以下是环视漫游动画的伪代码:
polyLineRoam(polyLine, time) {
const g3d = this.g3d;
const g3dDm = this.g3dDm;
this.roamButton.a('active', true);
this.roamAnim = ht.Default.startAnim({
duration: time,
easing: t => t,
action: (v, t) => {
let length = this.main.g3d.getLineLength(polyLine),
offset = this.main.g3d.getLineOffset(polyLine, length * v),
point = offset.point,
px = point.x,
py = point.y,
pz = point.z; g3d.setEye(px, py, pz);
g3d.setCenter(7, 40, 144);
},
finishFunc: () => {
this.roam1();
}
});
}
在整体建筑的环视漫游完后,我们可以通过拉近各个场景的视角,来依次巡视各个场景所执行的动画。在根据管道改变 eye 和 center 环视漫游方法结束后,用动画的结束回调 finishFunc 去调用下一个动画的执行,而巡视漫游就在这里去调用,以下我们以巡视冷站的漫游动画为例去介绍实现的方法。
巡视漫游的主要实现方法是通过 HT 核心包的相机移动 moveCamera 来实现的, 通过参数 (eye, center, animation) 来调用这个方法:
- eye:新的相机位置,形如[-291, -8, 283],如果为 null 则使用当前相机的位置;
- center:新的目标中心点位置(相机看向的位置),形如[148, -400, 171],如果为 null 则使用当前中心点位置;
- animation:默认 false,是否启用动画,可以设置为 true 或者 flase 或者 animation 动画对象;
每次执行完一个场景的视角移动后,再通过相机移动动画的结束回调 finishFunc 调用下一个相机移动的动画,达到巡视漫游的效果。
// 切换到冷站视角
roam1() {
const g3d = this.g3d;
const g3dDm = this.g3dDm;
this.roamAnim = g3d.moveCamera([-291, -8, 283], [148, -400, 171], {
duration: 500,
easing: t => t * t,
finishFunc: () => {
this.roam2();
}
});
}
在环视漫游和巡视漫游的执行下,我们也可以触发 2D 图纸右面板下的按钮面板去观看我们想要浏览的指定场景,这时候就会关闭当前在执行的环视漫游或者巡视漫游,再次点击改按钮则返回场景的主视角,或者点击左上角漫游按钮又可以进入环视漫游,这样的交互体验,可以方便用户即使地查看想要浏览的场景,而不用依靠等待逐一漫游下去查看,也不会干扰到漫游的整体体验。相应地通过介绍冷站按钮的点击触发介绍一下实现的方法。
一般的交互方式存在三种事件交互的方法,包括事件通知管理器 ht.Notifier 类,内置的 Interator 在交互过程会派发出事件和数据绑定的监听来实现,而这里使用的是第三种交互方式。
通过数据绑定监听到 onDown 执行按下的事件后,通过改变按下和再次按下的按钮状态 active 来分别执行相机移动去切换视角,主要实现的伪代码如下:
// 设置图元可交互
this.coolingCentralStationButton.s('interactive', true);
// 通过数据绑定监听到onDown执行按下的事件
this.coolingCentralStationButton.s('onDown', () => {
// 切换到冷站时,2d面板所执行的切换动画
this.switchToColdStation();
// 按钮初始化
this.buttonTearDown();
// 按钮按下效果的状态
let active = this.coolingCentralStationButton.a('active');
// button为按钮集合数组,当按下电梯按钮,其他按钮默认false
button.forEach(btn => {
btn.a('active', false);
});
// 冷站按钮的状态切换
this.coolingCentralStationButton.a('active', !active);
// 根据冷站按钮的状态执行切换到冷站或者切换回主视角
if (active) {
// 相机移动切换到主视角
moveCamera(g3d, [257, 713, 1485], [7, 40, 144], {
duration: 2000,
easing: t => t * t
});
} else {
// 漫游动画对象如果不为空,则暂停漫游动画对象并且设置为空
if (this.roamAnim !== null) {
this.roamAnim.pause();
this.roamAnim = null;
}
// 相机移动切换到冷站视角
coolingCentralStationAnimation = moveCamera(g3d, [-291, -8, 283], [148, -400, 171], {
duration: 2000,
easing: t => t * t
});
}
});
当然,在 3D 场景下还有一些很有趣的动画效果,比如车流效果、飞光效果和圆环扩散效果。车流效果主要通过采用了贴图的 uv 的偏移来实现达到车流穿梭的科技感效果;而飞光效果则是采用调度动画的方法来间隔设置飞光的高度,达到最高点则消失然后重新轮回动画展示;圆环扩散效果则是同样采用调度动画的方法来间隔设置圆环的缩放值和透明度,来达到扩散消失的效果。
对于间隔的调度动画,为了实现动画的流畅性,这里调度使用的 loop 是运用到自己封装 HT 的动画 ht.Default.startAnim 的一个方法:
- frames 动画帧数,这里不锁定帧数,可以适应本身动画的帧数;
- interval 动画间隔,单位ms,默认设置20ms。
loop(action, interval = 20) {
return ht.Default.startAnim({
frames: Infinity,
interval: interval,
action: action
});
}
然后通过调用这个 loop 的间隔动画方法,我们来实现车流效果、飞光效果和圆环扩散效果,实现的参考伪代码如下:
// 车流图元的初始化
let traffic = g3dDm.getDataByTag('traffic');
// 圆环扩散图元的初始化
let lightRing = this.lightRing = g3dDm.getDataByTag('lightRing');
// 飞光图元设置三种透明状态数组集合flyMap的初始化
[1, 2, 3].forEach(i => {
const data = flyMap['fly' + i] = g3dDm.getDataByTag('fly' + i);
data.eachChild(d => {
d.s({
// 打开透明度
'shape3d.transparent': true,
// 根据不同的数组集合设置不同的透明度
'shape3d.opacity': i === 3 ? 0.5 : 0.7,
// 设置沿着y轴自动旋转
'shape3d.autorotate': 'y'
});
});
}); if (this.flyAnim) return;
this.flyAnim = loop(() => {
// 飞光根据间隔设置高度来达到上升的效果
for (let k in flyMap) {
const data = flyMap[k];
let e = data.getElevation() + flyDltMap[k];
if (e >= 500) e = -400;
data.setElevation(e);
} // 车流根据设置间隔增长uv偏移量来实现穿梭的效果
traffic.eachChild(c => {
c.s('all.uv.offset', [location, 0]);
});
location -= 0.03; // 旋转震荡波透明度渐降
let percent = lightRing.a('percent') || 0,
scale = 15 * percent + 0.5;
lightRing.setScale3d([scale + 1, scale, scale + 1]);
lightRing.s('shape3d.opacity', (1 - percent) * 0.5);
percent += 0.01;
if (percent >= 1) {
percent = 0;
}
lightRing.a('percent', percent);
}, 50);
三、冷站场景和热站场景的动画实现
场景动画中机组的风扇、集水器的蓄满以及水的流动效果:
动画的实现主要还是通过 HT 自带的 ht.Default.startAnim 动画
函数,支持 Frame-Based 和 Time-Based 两种方式的动画。同样的,我们这里使用的是 Frame-Based 来封装一个 loop 函数来执行每一帧间隔的动画。
一般来说,动画可通过自行配置来达到自己想要实现的方法,这里可以了解< HT 的入门手册>关于动画
函数的介绍。
if (this.stationAnim) return;
this.stationAnim = loop(() => {
// 冷站水管流动
coldFlow_blue.eachChild(c => {
c.s('shape3d.uv.offset', [-location, 0]);
});
coldFlow_yellow.eachChild(c => {
c.s('shape3d.uv.offset', [location, 0]);
}); // 热站水管流动
heatFlow_blue.eachChild(c => {
c.s('shape3d.uv.offset', [-location, 0]);
});
heatFlow_yellow.eachChild(c => {
c.s('shape3d.uv.offset', [location, 0]);
}); location -= 0.03; // 冷站风扇旋转
cold_fan.eachChild(c => {
c.setRotation3d(c.r3()[0], c.r3()[1] + (Math.PI / 10), c.r3()[2]);
});
// 热站风扇旋转
heat_fan.eachChild(c => {
c.setRotation3d(c.r3()[0], c.r3()[1] + (Math.PI / 10), c.r3()[2]);
}); // 集水器水位变化
HotWaterTankTall += 0.25;
if (HotWaterTankTall > 15) {
HotWaterTankTall = 0;
}
coldWaterTankTall1 += 0.25;
if (coldWaterTankTall1 > 20) {
coldWaterTankTall1 = 0;
}
coldWaterTankTall2 += 0.25;
if (coldWaterTankTall2 > 20) {
coldWaterTankTall2 = 0;
}
hotWaterTank.setTall(HotWaterTankTall);
coldWaterTank1.setTall(coldWaterTankTall1);
coldWaterTank2.setTall(coldWaterTankTall2);
}, 50);
四、中央空调末端智慧群控系统场景效果
这里采用了模拟数据的方式来体现末端智能节能控制的效果。应用于真实项目的时候,可以采用数据接口的方式来实时对接真实数据,可以达到实时监控的效果。
我使用了自己 mock 的末端群控的数据参数,格式如下:
var boxData =
[
[{
// 设备编号
id: 'box1',
// 设备的温度
temperature: 23.8,
// 设备的频率
frequency: 45.8
}, ...]
...
];
这里的实现也是通过 loop 循环执行数据的读取,当数组指标 index 读取到最后一个数据时,立即关闭循环并清空 loop调度。
boxAnimation = loop(() => {
for (let i = 0, l = 16; i <= l-1; i++) {
let roomTag, roomBox, tag;
tag = i+1;
roomTag = 'boxPanel' + tag;
roomBox = 'box' + tag; let panel = g3dDm.getDataByTag(roomTag);
let box = g3dDm.getDataByTag(roomBox);
if (panel) {
panel.a('valueT', boxData[index][i].temperature + '℃');
panel.a('valueK', boxData[index][i].frequency + 'Hz');
// 手动更新缓存的面板信息
g3d.invalidateShape3dCachedImage(panel);
// 根据温度判断设备的颜色
if (box && parseFloat(panel.a('valueT')) < 26) {
box.s('shape3d.blend', 'rgb(4,67,176)');
box.s('wf.color', 'rgb(4,67,176)');
} else if (box && parseFloat(panel.a('valueT')) >= 26 && parseFloat(panel.a('valueT')) <= 28) {
box.s('shape3d.blend', 'rgb(28,189,87)');
box.s('wf.color', 'rgb(28,189,87)');
} else if (box && parseFloat(panel.a('valueT')) > 28) {
box.s('shape3d.blend', 'rgb(181,43,43)');
box.s('wf.color', 'rgb(181,43,43)');
}
}
}
index++;
if (index >= 10) {
boxAnimation.pause();
boxAnimation = null;
}
}, 500);
总结
基于 HTML5 WebGL 的楼宇智能化集成系统(一)的更多相关文章
- 基于 HTML5 WebGL 的楼宇智能化集成系统(二)
前言 一套完整的可视化操作交互上,必不可少 2D/3D 的融合,在上期我们介绍了有关 3D 场景的环视漫游.巡视漫游以及动画效果,还包括了冷站场景.热站场景以及智慧末端的实现原理,本期主要 ...
- 基于 HTML5 WebGL 的楼宇智能化集成系统(三)
前言 2018年7月,信息化部印发了<工业互联网平台建设及推广指南>和<工业互联网平台评价方法>,掀起了 工业互联网 的浪潮,并成为热词写入了报告中.同为信息发展下 ...
- 基于 HTML5 + WebGL 实现 3D 挖掘机系统
前言 在工业互联网以及物联网的影响下,人们对于机械的管理,机械的可视化,机械的操作可视化提出了更高的要求.如何在一个系统中完整的显示机械的运行情况,机械的运行轨迹,或者机械的机械动作显得尤为的重要,因 ...
- 基于 HTML5 WebGL 的 CPU 监控系统
前言 科技改变生活,科技的发展带来了生活方式的巨大改变.随着通信技术的不断演进,5G 技术应运而生,随时随地万物互联的时代已经来临.5G 技术不仅带来了更快的连接速度和前所未有的用户体验,也为制造业, ...
- 基于 HTML5 WebGL 的医疗物流系统
前言 物联网( IoT ),简单的理解就是物体之间通过互联网进行链接.世界上的万事万物,都可以通过数据的改变进行智能化管理.ioT 的兴起在医疗行业中具有拯救生命的潜在作用.不断的收集用户信息并且实时 ...
- 基于 HTML5 + WebGL 的 3D 太阳系系统
前言 近年来随着引力波的发现.黑洞照片的拍摄.火星上存在水的证据发现等科学上的突破,以及文学影视作品中诸如<三体>.<流浪地球>.<星际穿越>等的传播普及,宇宙空间 ...
- 基于 HTML5 + WebGL 实现的垃圾分类系统
前言 垃圾分类,一般是指按一定规定或标准将垃圾分类储存.分类投放和分类搬运,从而转变成公共资源的一系列活动的总称.分类的目的是提高垃圾的资源价值和经济价值,力争物尽其用.垃圾在分类储存阶段属于公众的私 ...
- 基于 HTML5 WebGL 的地铁站 3D 可视化系统
前言 工业互联网,物联网,可视化等名词在我们现在信息化的大背景下已经是耳熟能详,日常生活的交通,出行,吃穿等可能都可以用信息化的方式来为我们表达,在传统的可视化监控领域,一般都是基于 Web SCAD ...
- 基于 HTML5 WebGL 的计量站三维可视化监控系统 Web 组态工控应用
得益于 HTML5 WebGL 技术的成熟,从技术上对工控管理的可视化,数据可视化变得简单易行!完成对工控设备的管理效率,资源管理,风险管理等的大幅度提高,同时也对国家工业4.0计划作出有力响应! 如 ...
随机推荐
- CSS单位计算总结
CSS单位总结 公共部分css body { background-color: #000; color: skyblue; margin: 0; padding: 0; } body>div& ...
- Slog62_项目上线之ArthurSlog个人网站上线1
ArthurSlog SLog-62 Year·1 Guangzhou·China September 9th 2018 GitHub NPM Package Page ArthurSlog Page ...
- win10查看本机mac地址的详细操作
今天和大家分享win10查看本机mac地址的方法,mac地址是什么东西?MAC地址实际上就是网卡的一个标识,和身份证号码类似,大多数情况下是不需要关心MAC地址是多少的,一般不能改动,所以也不会重复. ...
- 关于有趣的windows.h
system 函数: 这个函数差不多就是调用 cmd (命令提示符). 当然,不一定要在程序中调用,用 txt 打入文本( 不用加system() )后改后缀名为 cmd 后运行即可. Win 键 + ...
- 复制url事故:出现特殊的字符%E2%80%8B
复制url事故:出现特殊的字符%E2%80%8B 问题:直接其他地方复制过来的中文字进行网页搜索.或者中文字识别排序等情况的,会出现搜索不到的情况. 解决方法:可能存在复制源里面的文字带了空白url编 ...
- 2020年ubuntu sever1804 安装和配置
最后一次折腾linux服务器,应该是13的我的VPS.因为转行后,没有及时关注vps续费的问题,结果过期,所有的数据丢失了 当时觉得,反正都不做了,丢了就丢了吧,可现在想起来,实在是太后悔了. 今天, ...
- 编译 openwrt 及初始配置
主机为 ubuntu 14 x64 硬件: 优酷土豆宝 cpuMT7620A,内存128M,flash 32M有2个源,用哪个也可以git clone https://github.com/openw ...
- 结题报告--P2441角色属性树
题目:点此 题目描述 绪萌同人社是一个有趣的组织,该组织结构是一个树形结构.有一个社长,直接下属一些副社长.每个副社长又直接下属一些部长……. 每个成员都有一个萌点的属性,萌点属性是由一些质数的萌元素 ...
- React-redux: React.js 和 Redux 架构的结合
通过Redux 架构理解我们了解到 Redux 架构的 store.action.reducers 这些基本概念和工作流程.我们也知道了 Redux 这种架构模式可以和其他的前端库组合使用,而 Rea ...
- Java反射之构造方法反射
上一篇Java反射之Class类我们介绍了java反射的关键类Class, 反射就是由一个java类映射得到一个java类. 所以,我们自然能想到,一个类中应该有哪些属性,这里做个比方,人有名字年龄等 ...