基于 HTML5 WebGL 的楼宇智能化集成系统(一)
<div><span data-mce-=""><strong>前言</strong></span></div><div> 随着现代通信技术、计算机技术、控制技术的飞速发展,智能建筑已经成为现代建筑发展的主流。智能建筑是以建筑物为平台,兼备信息设施系统、信息化应用系统、建筑设备管理系统、公共安全系统等。集结构、系统、服务、管理及其优化组合为一体,向人们提供一个安全、高效、便携、节能、环保、健康的建筑环境。</div>
<div> </div>
<div> <strong>IBMS</strong>(Intelligent Building Management System),即智能化集成系统,是指在 BAS 的基础上更进一步的与通信网络系统、信息网络系统实现更高一层的建筑集成管理系统。在信息化时代的今天,诸多建筑与集成的管理系统融合,生成了一套高效的智慧建筑集成解决方案。IBMS 更多突出的是管理方面的功能,即如何的全面实现优化控制和管理,节能降耗、高效、舒适、环境安全这样一个目的,可以这样说,判断一个建筑物是否具有智能建筑特点,要看它是否具有 IBMS 的系统集成。这是很重要的判定条件。另一个重要的前提是,在做好这项工程的同时不要忽视了同步建设的信息化工程。一个成功的 IBMS 系统集成会在诸多的管理方面能发挥其显著的经济优势。</div>
<div> </div>
<div> 传统的 智慧楼宇/楼宇自动化/楼宇安防/智慧园区 常会采用 BIM(建筑信息模型 Building information modeling)软件,如 Autodesk 的 Revit 或 Bentley 这类建筑和工程软件,但这些 BIM 建模模型的数据往往过于庞大臃肿,绝大部分细节信息对楼宇自控意义不大,反而影响拖累了行业 Web SCADA 或 Web 组态监控的趋势,所以我们采用以 <strong>Hightopo</strong> 的<strong> HT for Web</strong> 产品轻量化 HTML5/WebGL 建模的方案,实现快速建模、运行时轻量化到甚至手机终端浏览器即可 3D 可视化运维的良好效果。</div>
<div> </div>
<div> 本系列文章为了帮助用户更直观友好的浏览当前的楼宇智控系统,分成了三个小节来介绍场景以及效果实现的运用:</div>
<div>1、<strong>冷站</strong>,<strong>热站</strong>,<strong>中央空调末端智慧群控系统 </strong>以及 3D 动画效果以及切换漫游;</div>
<div>2、面板组件动画效果和 <strong>楼层监控系统 </strong>视频的引入;</div>
<div>3、<strong>智慧楼宇管理系统</strong>、<strong>电梯监控系统 </strong>以及 <strong>停车场管理系统</strong>;</div>
<div> </div>
<div>
<div><span data-mce-=""><strong>界面简介及效果预览</strong></span></div>
<div><strong><span data-mce-="">界面初始化及漫游效果</span></strong></div>
<div> 场景在加载的时候会读取模型信息,确认模型加载完毕后才开始执行动画效果,然后通过场景的漫游效果,可以直观地去巡视整座大楼建筑的场景信息。</div>
<div><img src="https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200405041331985-437729178.gif"></div>
<div> </div>
<div><img src="https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200405041349178-333364888.gif"></div>
<div> </div>
<div><strong><span data-mce-="">冷站场景效果:优化冷水机组效率,按需供给</span></strong></div>
<div> 在智慧楼宇的中央空调冷热源系统中,冷站场景分有冷却水系统、冷水机组以及冷冻水系统所组成。冷却水系统的作用是为冷水机组的冷凝器提供冷却水,吸收制冷剂的冷凝热量,并将冷凝热量转移到大气中去。冷冻水系统的作用是为冷水机组的蒸发器提供的冷量通过冷冻水输送到各类冷水用户。</div>
<div> <img src="https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200405041414883-1353782115.gif"></div>
<div> </div>
<div><strong><span data-mce-="">热站场景效果:优化热泵机组效率,按需供给</span></strong></div>
<div> 在智慧楼宇的中央空调冷热源系统中,热站场景分有冷却机组系统和热水泵系统所组成。通过冷却机组系统的换热器不断加热了中央空调系统内的空调水,并通过热水循环中的热水泵系统进行循环给用户提供热量。</div>
<div><img src="https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200405041426434-899743081.gif"></div>
<div> </div>
<div><strong><span data-mce-="">中央空调末端智慧群控系统场景效果:灵活对应多样智能调节空间</span></strong></div>
<div> 在智慧楼宇的中央空调冷热源系统中,末端智能节能控制系统,通过室内温湿度可以进行模块内部的调节送风温度,水阀开度及风机频率,在保证末端舒适度的前提下,使供冷量与需求相匹配,最大限度地降低风机能耗。</div>
<div><img src="https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200405041437284-1609033361.gif"></div>
<div> </div>
<div>
<div><span data-mce-=""><strong>智慧楼宇管理系统优化效果</strong></span></div>
<div> 主要包括冷站、热站和中央末端智慧群控的联合作用,以及楼层智慧照明,通过清晰的动画体现出整栋大楼智慧节能运作的流程,可以通过面板详情的演示细致地介绍每个场景的作用以及串联的用处。</div>
<div><img src="https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200405044251490-168296163.gif"></div>
<div> </div>
<div><span data-mce-=""><strong>电梯以及楼层监控效果</strong></span></div>
<div> 可视化地实时监控电梯在楼层间的工作运行状态,并且能够准确地浏览每个电梯内的实时监控画面。</div>
<div> <img src="https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200405041447282-1554934597.gif"></div>
<div> </div>
<div><strong><span data-mce-="">停车场管理系统监控效果</span></strong></div>
<div> 停车场作为现在楼宇监控不可缺失的一环,这里主要可以体现出实时的车位监控,通过简单的动画演示来表现出整个停车场车辆的运行状态,方便管理。</div>
<div><img src="https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200405041458355-1726495088.gif"></div>
<div> </div>
<div>
<div><span data-mce-=""><strong>代码实现</strong></span></div>
<div><span data-mce-=""><strong>一、场景搭建</strong></span></div>
<div> 界面通过 2D 图纸叠加在3D 场景上来实现 2D 界面 与 3D 场景的融合,2D 界面通过自动布局的机制实现了手机端与电脑端的响应式呈现。</div>
<div> 通过 2D 视图的组件 <strong>ht.graph.</strong><span id="anchor_8"><strong>GraphView</strong> 和 3D 视图的组件 <strong>ht.graph3d.</strong><span id="anchor_8"><strong>Graph3dView</strong> 创建出呈现 2D 视图的组件类 g2d 以及呈现 3D 视图的组件类 g3d,在分别获取各自的数据模型 DataModel,来对图纸场景做一些数据可视化的操作,这里值得一提的是,我对于 2D 界面和 3D 场景的融合,是通过把 getView() 获取到 g3d 拓扑组件的根层 div,然后 addToDOM() 将 g2d 组件加入到指定的 DOM 元素底下。</span></span></div>
<div><span id="anchor_8"> 可以通过<HT的入门手册>了解到更多视图与数据模型之间的内容。</span></div>
<div>
<div class="cnblogs_code">
<pre><span data-mce-="">//<span data-mce-=""> 创建二维拓扑视图
<span data-mce-="">this.g2d = <span data-mce-="">new<span data-mce-=""> ht.graph.GraphView();
<span data-mce-="">this.g2dDm = <span data-mce-="">this<span data-mce-="">.g2d.dm();
<span data-mce-="">//<span data-mce-=""> 创建三维拓扑视图
<span data-mce-="">this.g3d = <span data-mce-="">new<span data-mce-=""> ht.graph3d.Graph3dView();
<span data-mce-="">this.g3dDm = <span data-mce-="">this<span data-mce-="">.g3d.dm();
<span data-mce-="">//<span data-mce-=""> 将二维图纸嵌入到三维场景中
<span data-mce-="">this.g2d.addToDOM(<span data-mce-="">this<span data-mce-="">.g3d.getView());
<span data-mce-="">//<span data-mce-=""> 修改左右键交互方式
let mapInteractor = <span data-mce-="">new ht.graph3d.MapInteractor(<span data-mce-="">this<span data-mce-="">.g3d);
<span data-mce-="">this<span data-mce-="">.g3d.setInteractors();
<span data-mce-="">//<span data-mce-=""> 修改最大仰角为 PI / 2
mapInteractor.maxPhi = Math.PI / 2<span data-mce-="">;
const G =<span data-mce-=""> {};
window.G =<span data-mce-=""> G;
<span data-mce-="">//<span data-mce-=""> 事件派发
G.event = <span data-mce-="">new ht.Notifier();</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p> </p>
</div>
<div>
<p> </p>
<p>3D 场景加载主视图为:</p>
<p><img src="https://img2018.cnblogs.com/common/1496396/202002/1496396-20200220212404928-750312682.png"></p>
<p> </p>
<p> 首先我搭建了一个 3D 的场景用来放置我们的 json 场景数据,利用 <strong>ht.Default.xhrLoad</strong> 函数解析 json 场景数据,并通过 deserialize 将反序列化的对象加入DataModel来显示加载 3D 场景,有兴趣的可以通过<HT的序列化手册>来了解这一机制的实现。</p>
<div class="cnblogs_code">
<pre>ht.Default.xhrLoad('scenes/demo.json', (json) =><span data-mce-=""> {
<span data-mce-="">if (!json) <span data-mce-="">return<span data-mce-="">;
g3dDm.deserialize(json);
<span data-mce-="">//<span data-mce-=""> 设置三维视图的中心点和相机位置
g3d.setCenter([-342, -64, 389<span data-mce-="">]);
g3d.setEye([-355, 10833, 2642<span data-mce-="">]);
<span data-mce-="">//<span data-mce-=""> 设置最远距离
g3d.setFar(1000000<span data-mce-="">);
<span data-mce-="">//<span data-mce-=""> 获取球图标,设置为天空球
let skybox = g3dDm.getDataByTag('skyBox'<span data-mce-="">);
g3d.setSkyBox(skybox);
<span data-mce-="">//<span data-mce-=""> 模型加载完后执行动画
const modelList =<span data-mce-=""> [];
g3dDm.each(d =><span data-mce-=""> {
const shape3d = d.s('shape3d'<span data-mce-="">);
<span data-mce-="">if (!shape3d || !shape3d.endsWith('.json')) <span data-mce-="">return<span data-mce-="">;
<span data-mce-="">if (ht.Default.getShape3dModel(shape3d)) <span data-mce-="">return<span data-mce-="">;
modelList.push(shape3d);
});
ht.Default.handleModelLoaded = (name, model) =><span data-mce-=""> {
const index =<span data-mce-=""> modelList.indexOf(name);
<span data-mce-="">if (index < 0) <span data-mce-="">return<span data-mce-="">;
modelList.splice(index, 1<span data-mce-="">);
<span data-mce-="">if (modelList.length > 91) <span data-mce-="">return<span data-mce-="">;
ht.Default.handleModelLoaded = () =><span data-mce-=""> {
};
<span data-mce-="">//<span data-mce-=""> 模型加载完侯,默认执行场景切换动画
g3d.moveCamera(, , {
duration: 2000<span data-mce-="">,
finishFunc: () =><span data-mce-=""> {
<span data-mce-="">this<span data-mce-="">.load2D();
}
});
};
});</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p> </p>
<p>2D 面板加载视图为:</p>
<p><img src="https://img2018.cnblogs.com/common/1496396/202002/1496396-20200220212650241-1122491087.png"></p>
<p> </p>
<p> 同样,我搭建了一个 2D 的场景用来放置我们的 json 矢量图,利用 <strong>ht.Default.xhrLoad</strong> 函数将 json 矢量背景图反序列化显示在 2D 面板数据。</p>
<div class="cnblogs_code">
<pre>ht.Default.xhrLoad('displays/demo.json', (json) =><span data-mce-=""> {
<span data-mce-="">if (!json) <span data-mce-="">return<span data-mce-="">;
g2dDm.deserialize(json);
<span data-mce-="">//<span data-mce-=""> 面板动画入口
<span data-mce-="">this<span data-mce-="">.tittleAnim();
<span data-mce-="">this<span data-mce-="">.panelTime();
<span data-mce-="">//<span data-mce-=""> 2D图纸加载完后执行事件处理
<span data-mce-="">this<span data-mce-="">.loaded2DHandler();
});</span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p> </p>
<p> </p>
<p><strong><span data-mce-="">二、3D 动画效果以及切换漫游</span></strong></p>
<p> 对于 3D 建模下的楼宇建筑,加上场景的全方位漫游,可使用户达到一种沉浸式的体验,更加直观地去感受这个楼宇下各个场景的联系,依次地介绍了冷站、智慧末端以及热站的位置以及功能运作的动画 。主要运用的方法是通过借助 <strong>HT</strong> 提供的 <strong>ht.Shape</strong> 图元类型,可以在 <strong>GraphView</strong> 和 <strong>Graph3dView</strong> 组件上展示出各种二维和三维的形状效果,而漫游的管道路线就是由其扩展子类 <strong>ht.Polyline</strong> 去绘制实现一条三维的管道,然后用这条绘制的管道加上漫游的时间去调用这个漫游的方法,其本质上是围绕着中心点,然后根据管道去不断地改变视角下的 <strong>eye</strong> 和 <strong>center</strong> 的数值,达到环视这个建筑的整体视角。</p>
<p><img src="https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200406222724773-1219355409.gif"></p>
<p> 这里可以了解一下关于空间轨道的绘制,详见<HT的形状手册>的空间管线章节。</p>
<p> 以下是环视漫游动画的伪代码:</p>
<div class="cnblogs_code">
<pre><span data-mce-="">polyLineRoam(polyLine, time) {
const g3d = <span data-mce-="">this<span data-mce-="">.g3d;
const g3dDm = <span data-mce-="">this<span data-mce-="">.g3dDm;
<span data-mce-="">this.roamButton.a('active', <span data-mce-="">true<span data-mce-="">);
<span data-mce-="">this.roamAnim =<span data-mce-=""> ht.Default.startAnim({
duration: time,
easing: t =><span data-mce-=""> t,
action: (v, t) =><span data-mce-=""> {
let length = <span data-mce-="">this<span data-mce-="">.main.g3d.getLineLength(polyLine),
offset = <span data-mce-="">this.main.g3d.getLineOffset(polyLine, length *<span data-mce-=""> v),
point =<span data-mce-=""> offset.point,
px =<span data-mce-=""> point.x,
py =<span data-mce-=""> point.y,
pz =<span data-mce-=""> point.z;
g3d.setEye(px, py, pz);
g3d.setCenter(7, 40, 144<span data-mce-="">);
},
finishFunc: () =><span data-mce-=""> {
<span data-mce-="">this<span data-mce-="">.roam1();
}
});
}</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p> </p>
<p> 在整体建筑的环视漫游完后,我们可以通过拉近各个场景的视角,来依次巡视各个场景所执行的动画。在根据管道改变 <strong>eye </strong>和 <strong>center</strong> 环视漫游方法结束后,用动画的结束回调 <strong>finishFunc</strong> 去调用下一个动画的执行,而巡视漫游就在这里去调用,以下我们以巡视冷站的漫游动画为例去介绍实现的方法。</p>
<p> 巡视漫游的主要实现方法是通过 <strong>HT</strong> 核心包的相机移动 <strong>moveCamera</strong> 来实现的, 通过参数 (eye, center, animation) 来调用这个方法:</p>
<ul>
<li><strong>eye</strong>:新的相机位置,形如[-291, -8, 283],如果为 null 则使用当前相机的位置;</li>
<li><strong>center</strong>:新的目标中心点位置(相机看向的位置),形如,如果为 null 则使用当前中心点位置;</li>
<li><strong>animation</strong>:默认 false,是否启用动画,可以设置为 true 或者 flase 或者 animation 动画对象;</li>
</ul>
<p> 每次执行完一个场景的视角移动后,再通过相机移动动画的结束回调 finishFunc 调用下一个相机移动的动画,达到巡视漫游的效果。</p>
<div class="cnblogs_code">
<pre><span data-mce-="">//<span data-mce-=""> 切换到冷站视角
<span data-mce-="">roam1() {
const g3d = <span data-mce-="">this<span data-mce-="">.g3d;
const g3dDm = <span data-mce-="">this<span data-mce-="">.g3dDm;
<span data-mce-="">this.roamAnim = g3d.moveCamera([-291, -8, 283], , {
duration: 500<span data-mce-="">,
easing: t => t *<span data-mce-=""> t,
finishFunc: () =><span data-mce-=""> {
<span data-mce-="">this<span data-mce-="">.roam2();
}
});
}</span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p> </p>
<p> 在环视漫游和巡视漫游的执行下,我们也可以触发 2D 图纸右面板下的按钮面板去观看我们想要浏览的指定场景,这时候就会关闭当前在执行的环视漫游或者巡视漫游,再次点击改按钮则返回场景的主视角,或者点击左上角漫游按钮又可以进入环视漫游,这样的交互体验,可以方便用户即使地查看想要浏览的场景,而不用依靠等待逐一漫游下去查看,也不会干扰到漫游的整体体验。相应地通过介绍冷站按钮的点击触发介绍一下实现的方法。</p>
<p><img src="https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200406223113387-1609822143.gif"></p>
<p> 一般的交互方式存在三种事件交互的方法,包括事件通知管理器 ht.Notifier 类,内置的 Interator 在交互过程会派发出事件和数据绑定的监听来实现,而这里使用的是第三种交互方式。</p>
<p> 通过数据绑定监听到 onDown 执行按下的事件后,通过改变按下和再次按下的按钮状态 active 来分别执行相机移动去切换视角,主要实现的伪代码如下:</p>
<div class="cnblogs_code">
<pre><span data-mce-="">//<span data-mce-=""> 设置图元可交互
<span data-mce-="">this.coolingCentralStationButton.s('interactive', <span data-mce-="">true<span data-mce-="">);
<span data-mce-="">//<span data-mce-=""> 通过数据绑定监听到onDown执行按下的事件
<span data-mce-="">this.coolingCentralStationButton.s('onDown', () =><span data-mce-=""> {
<span data-mce-="">//<span data-mce-=""> 切换到冷站时,2d面板所执行的切换动画
<span data-mce-="">this<span data-mce-="">.switchToColdStation();
<span data-mce-="">//<span data-mce-=""> 按钮初始化
<span data-mce-="">this<span data-mce-="">.buttonTearDown();
<span data-mce-="">//<span data-mce-=""> 按钮按下效果的状态
let active = <span data-mce-="">this.coolingCentralStationButton.a('active'<span data-mce-="">);
<span data-mce-="">//<span data-mce-=""> button为按钮集合数组,当按下电梯按钮,其他按钮默认false
button.forEach(btn =><span data-mce-=""> {
btn.a('active', <span data-mce-="">false<span data-mce-="">);
});
<span data-mce-="">//<span data-mce-=""> 冷站按钮的状态切换
<span data-mce-="">this.coolingCentralStationButton.a('active', !<span data-mce-="">active);
<span data-mce-="">//<span data-mce-=""> 根据冷站按钮的状态执行切换到冷站或者切换回主视角
<span data-mce-="">if<span data-mce-=""> (active) {
<span data-mce-="">//<span data-mce-=""> 相机移动切换到主视角
moveCamera(g3d, , , {
duration: 2000<span data-mce-="">,
easing: t => t *<span data-mce-=""> t
});
} <span data-mce-="">else<span data-mce-=""> {
<span data-mce-="">//<span data-mce-=""> 漫游动画对象如果不为空,则暂停漫游动画对象并且设置为空
<span data-mce-="">if (<span data-mce-="">this.roamAnim !== <span data-mce-="">null<span data-mce-="">) {
<span data-mce-="">this<span data-mce-="">.roamAnim.pause();
<span data-mce-="">this.roamAnim = <span data-mce-="">null<span data-mce-="">;
}
<span data-mce-="">//<span data-mce-=""> 相机移动切换到冷站视角
coolingCentralStationAnimation = moveCamera(g3d, [-291, -8, 283], , {
duration: 2000<span data-mce-="">,
easing: t => t *<span data-mce-=""> t
});
}
});</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p> </p>
<p> 当然,在 3D 场景下还有一些很有趣的动画效果,比如车流效果、飞光效果和圆环扩散效果。车流效果主要通过采用了贴图的 uv 的偏移来实现达到车流穿梭的科技感效果;而飞光效果则是采用调度动画的方法来间隔设置飞光的高度,达到最高点则消失然后重新轮回动画展示;圆环扩散效果则是同样采用调度动画的方法来间隔设置圆环的缩放值和透明度,来达到扩散消失的效果。</p>
<p><img src="https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200406221245931-2050879216.gif"></p>
<p> 对于间隔的调度动画,为了实现动画的流畅性,这里调度使用的 loop 是运用到自己封装 <strong>HT</strong> 的动画 <strong>ht.Default.startAnim</strong> 的一个方法:</p>
<ul>
<li><strong>frames </strong>动画帧数,这里不锁定帧数,可以适应本身动画的帧数;</li>
<li><strong>interval </strong>动画间隔,单位ms,默认设置20ms。</li>
</ul>
<div class="cnblogs_code">
<pre>loop(action, interval = 20<span data-mce-="">) {
<span data-mce-="">return<span data-mce-=""> ht.Default.startAnim({
frames: Infinity,
interval: interval,
action: action
});
}</span></span></span></pre>
</div>
<p> </p>
<p> </p>
<p> 然后通过调用这个 loop 的间隔动画方法,我们来实现车流效果、飞光效果和圆环扩散效果,实现的参考伪代码如下:</p>
<div class="cnblogs_code">
<pre><span data-mce-="">//<span data-mce-=""> 车流图元的初始化
let traffic = g3dDm.getDataByTag('traffic'<span data-mce-="">);
<span data-mce-="">//<span data-mce-=""> 圆环扩散图元的初始化
let lightRing = <span data-mce-="">this.lightRing = g3dDm.getDataByTag('lightRing'<span data-mce-="">);
<span data-mce-="">//<span data-mce-=""> 飞光图元设置三种透明状态数组集合flyMap的初始化
.forEach(i =><span data-mce-=""> {
const data = flyMap['fly' + i] = g3dDm.getDataByTag('fly' +<span data-mce-=""> i);
data.eachChild(d =><span data-mce-=""> {
d.s({
<span data-mce-="">//<span data-mce-=""> 打开透明度
'shape3d.transparent': <span data-mce-="">true<span data-mce-="">,
<span data-mce-="">//<span data-mce-=""> 根据不同的数组集合设置不同的透明度
'shape3d.opacity': i === 3 ? 0.5 : 0.7<span data-mce-="">,
<span data-mce-="">//<span data-mce-=""> 设置沿着y轴自动旋转
'shape3d.autorotate': 'y'<span data-mce-="">
});
});
});
<span data-mce-="">if (<span data-mce-="">this.flyAnim) <span data-mce-="">return<span data-mce-="">;
<span data-mce-="">this.flyAnim = loop(() =><span data-mce-=""> {
<span data-mce-="">//<span data-mce-=""> 飞光根据间隔设置高度来达到上升的效果
<span data-mce-="">for (let k <span data-mce-="">in<span data-mce-=""> flyMap) {
const data =<span data-mce-=""> flyMap;
let e = data.getElevation() +<span data-mce-=""> flyDltMap;
<span data-mce-="">if (e >= 500) e = -400<span data-mce-="">;
data.setElevation(e);
}
<span data-mce-="">//<span data-mce-=""> 车流根据设置间隔增长uv偏移量来实现穿梭的效果
traffic.eachChild(c =><span data-mce-=""> {
c.s('all.uv.offset', );
});
location -= 0.03<span data-mce-="">;
<span data-mce-="">//<span data-mce-=""> 旋转震荡波透明度渐降
let percent = lightRing.a('percent') || 0<span data-mce-="">,
scale = 15 * percent + 0.5<span data-mce-="">;
lightRing.setScale3d();
lightRing.s('shape3d.opacity', (1 - percent) * 0.5<span data-mce-="">);
percent += 0.01<span data-mce-="">;
<span data-mce-="">if (percent >= 1<span data-mce-="">) {
percent = 0<span data-mce-="">;
}
lightRing.a('percent'<span data-mce-="">, percent);
}, 50);</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p> </p>
<p><span data-mce-=""><strong>三、冷站场景和热站场景的动画实现</strong></span></p>
<p> 场景动画中机组的风扇、集水器的蓄满以及水的流动效果:</p>
<p><img src="https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200406221013712-1564093786.gif"></p>
<p> 动画的实现主要还是通过 <strong>HT</strong> 自带的 <strong>ht.Default.startAnim</strong> <code>动画</code>函数,支持 Frame-Based 和 Time-Based 两种方式的动画。同样的,我们这里使用的是 Frame-Based 来封装一个 loop 函数来执行每一帧间隔的动画。</p>
<p> 一般来说,动画可通过自行配置来达到自己想要实现的方法,这里可以了解< HT 的入门手册>关于<code>动画</code>函数的介绍。</p>
<div class="cnblogs_code">
<pre><span data-mce-="">if (<span data-mce-="">this.stationAnim) <span data-mce-="">return<span data-mce-="">;
<span data-mce-="">this.stationAnim = loop(() =><span data-mce-=""> {
<span data-mce-="">//<span data-mce-=""> 冷站水管流动
coldFlow_blue.eachChild(c =><span data-mce-=""> {
c.s('shape3d.uv.offset', [-location, 0<span data-mce-="">]);
});
coldFlow_yellow.eachChild(c =><span data-mce-=""> {
c.s('shape3d.uv.offset', );
});
<span data-mce-="">//<span data-mce-=""> 热站水管流动
heatFlow_blue.eachChild(c =><span data-mce-=""> {
c.s('shape3d.uv.offset', [-location, 0<span data-mce-="">]);
});
heatFlow_yellow.eachChild(c =><span data-mce-=""> {
c.s('shape3d.uv.offset', );
});
location -= 0.03<span data-mce-="">;
<span data-mce-="">//<span data-mce-=""> 冷站风扇旋转
cold_fan.eachChild(c =><span data-mce-=""> {
c.setRotation3d(c.r3(), c.r3() + (Math.PI / 10), c.r3());
});
<span data-mce-="">//<span data-mce-=""> 热站风扇旋转
heat_fan.eachChild(c =><span data-mce-=""> {
c.setRotation3d(c.r3(), c.r3() + (Math.PI / 10), c.r3());
});
<span data-mce-="">//<span data-mce-=""> 集水器水位变化
HotWaterTankTall += 0.25<span data-mce-="">;
<span data-mce-="">if (HotWaterTankTall > 15<span data-mce-="">) {
HotWaterTankTall = 0<span data-mce-="">;
}
coldWaterTankTall1 += 0.25<span data-mce-="">;
<span data-mce-="">if (coldWaterTankTall1 > 20<span data-mce-="">) {
coldWaterTankTall1 = 0<span data-mce-="">;
}
coldWaterTankTall2 += 0.25<span data-mce-="">;
<span data-mce-="">if (coldWaterTankTall2 > 20<span data-mce-="">) {
coldWaterTankTall2 = 0<span data-mce-="">;
}
hotWaterTank.setTall(HotWaterTankTall);
coldWaterTank1.setTall(coldWaterTankTall1);
coldWaterTank2.setTall(coldWaterTankTall2);
}, 50);</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p> </p>
<p> </p>
<div>
<p><span data-mce-=""><strong>四、中央空调末端智慧群控系统场景效果</strong></span></p>
<p> 这里采用了模拟数据的方式来体现末端智能节能控制的效果。应用于真实项目的时候,可以采用数据接口的方式来实时对接真实数据,可以达到实时监控的效果。</p>
<p><img src="https://img2020.cnblogs.com/blog/1496396/202004/1496396-20200406220400372-1396060721.gif"></p>
<p> 我使用了自己 mock 的末端群控的数据参数,格式如下:</p>
<div class="cnblogs_code">
<pre><span data-mce-="">var boxData =<span data-mce-="">
[
[{
<span data-mce-="">//<span data-mce-=""> 设备编号
id: 'box1'<span data-mce-="">,
<span data-mce-="">//<span data-mce-=""> 设备的温度
temperature: 23.8<span data-mce-="">,
<span data-mce-="">//<span data-mce-=""> 设备的频率
frequency: 45.8<span data-mce-="">
}, ...]
...
];</span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p> </p>
<p> </p>
<p> 这里的实现也是通过 loop 循环执行数据的读取,当数组指标 index 读取到最后一个数据时,立即关闭循环并清空 loop调度。</p>
<div class="cnblogs_code">
<pre>boxAnimation = loop(() =><span data-mce-=""> {
<span data-mce-="">for (let i = 0, l = 16; i <= l-1; i++<span data-mce-="">) {
let roomTag, roomBox, tag;
tag = i+1<span data-mce-="">;
roomTag = 'boxPanel' +<span data-mce-=""> tag;
roomBox = 'box' +<span data-mce-=""> tag;
let panel =<span data-mce-=""> g3dDm.getDataByTag(roomTag);
let box =<span data-mce-=""> g3dDm.getDataByTag(roomBox);
<span data-mce-="">if<span data-mce-=""> (panel) {
panel.a('valueT', boxData.temperature + '℃'<span data-mce-="">);
panel.a('valueK', boxData.frequency + 'Hz'<span data-mce-="">);
<span data-mce-="">//<span data-mce-=""> 手动更新缓存的面板信息
<span data-mce-=""> g3d.invalidateShape3dCachedImage(panel);
<span data-mce-="">//<span data-mce-=""> 根据温度判断设备的颜色
<span data-mce-="">if (box && parseFloat(panel.a('valueT')) < 26<span data-mce-="">) {
box.s('shape3d.blend', 'rgb(4,67,176)'<span data-mce-="">);
box.s('wf.color', 'rgb(4,67,176)'<span data-mce-="">);
} <span data-mce-="">else <span data-mce-="">if (box && parseFloat(panel.a('valueT')) >= 26 && parseFloat(panel.a('valueT')) <= 28<span data-mce-="">) {
box.s('shape3d.blend', 'rgb(28,189,87)'<span data-mce-="">);
box.s('wf.color', 'rgb(28,189,87)'<span data-mce-="">);
} <span data-mce-="">else <span data-mce-="">if (box && parseFloat(panel.a('valueT')) > 28<span data-mce-="">) {
box.s('shape3d.blend', 'rgb(181,43,43)'<span data-mce-="">);
box.s('wf.color', 'rgb(181,43,43)'<span data-mce-="">);
}
}
}
index++<span data-mce-="">;
<span data-mce-="">if (index >= 10<span data-mce-="">) {
boxAnimation.pause();
boxAnimation = <span data-mce-="">null<span data-mce-="">;
}
}, 500);</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></pre>
</div>
<p> </p>
<p> </p>
<p><span data-mce-=""><strong>总结</strong></span></p>
<div> IBMS 智能化集成系统管理对于建筑园区管理的重要性日趋上升,在信息时代里不仅可以很好地体现出信息数据管理的明确性,也体现了智慧管理的便利有效性。通过 3D 场景楼宇园区的动画加上环视漫游和巡视漫游的配合,充分体现了 3D 场景的拟真优点,但是如何实现场景动画的触发实现呢?这里当然必不可少了 2D 面板上的交互和动画,在下期我们会为大家介绍一些 2D 面板的交互和动画实现,带您解读不一样的 2D/3D 融合。</div>
<div><img src="https://img2018.cnblogs.com/common/1496396/202002/1496396-20200225200053329-1065733339.jpg"></div>
<div> </div>
<div> 2019 我们也更新了数百个工业互联网 2D/3D 可视化案例集,在这里你能发现许多新奇的实例,也能发掘出不一样的工业互联网:https://mp.weixin.qq.com/s/ZbhB6LO2kBRPrRIfHlKGQA</div>
<div> 同时,你也可以查看更多案例及效果:<strong>https://www.hightopo.com/demos/index.html</strong></div>
</div>
</div>
</div>
</div>
</div><br><br>
来源:https://www.cnblogs.com/xhload3d/p/12650621.html
頁:
[1]