前言

在当代社会,故宫已经成为一个具有多元意义的文化符号,在历史、艺术、文化等不同领域发挥着重要的作用,在国际上也成为能够代表中国文化甚至中国形象的国际符号。近几年故宫的观众接待量逐年递增,年接待量已突破千万,根据故宫的文物特点与开放模式,必须及时建立一套完整的集监控与防患应急于一体的现代化监控系统。

故宫人流量动态监控系统采用 Hightopo 的 HT for Web 产品来构造 故宫 3D 动态可视化场景,通过将现场部署的传感器、监控设备等装置与智能联网设备集成到互联网上,对故宫当前的人流状态、人流拥挤度进行实时监测,并生成人流量热力图直观的展示现场人流数据,以预防拥挤、踩踏等意外事故的发生。

预览地址:故宫人流量动态监控系统

整体预览图:

全景图预览:

代码实现

创建场景

项目目录结构如下:

index.js 是 src 下的入口文件,创建了一个 由 main.js 中导出的 Main 类,Main 类中创建 3D 组件和 2D 组件,利用 g2d.deserialize() 方法将 json 矢量背景图反序列化显示在 2D 组件上并利用 this.load() 方法进行 3D 场景的加载工作,在 Main 类中使用了 HT 自带的事件派发器,this.event.fire() 和 this.event.add() 分别是派发事件和订阅事件,在本示例中通过事件订阅与派发完成3D场景的切换效果,关键代码如下:

import util from '../util/util';
import forbiddenCity from './forbiddenCity.js' import heatMap from './heatMap.js' import loadScene from './loadScene.js'
class Main {constructor() {let g3d = (this.g3d = new ht.graph3d.Graph3dView()); this.g3dDm = this.g3d.dm();let g2d = (this.g2d = new ht.graph.GraphView()); this.g2dDm = this.g2d.dm(); //将 3D 组件加入到 body 下g3d.addToDOM(); // 将 2D 组件加入到 3D 组件的根 div 下,父子 DOM 事件会冒泡,这样不会影响 3D 场景的交互g2d.addToDOM(g3d.getView()); // 初始化场景this.init();}init() { // 2D面板加载this.g2d.deserialize('displays/htproject_2019_q4/故宫/首页.json', (json, dm, g2d, datas) => {  }); this.forbiddenCity = new forbiddenCity(this); this.heatMap = new heatMap(this); // 首页3D场景加载this.load(this.forbiddenCity); // 订阅事件this.addListener(e => { if (e.type === 'loadforbiddenCity') { this.load(this.forbiddenCity);} else if (e.type === 'loadheatMap') { this.load(this.heatMap);}});}load(scene) {let old = this.activeScene; if (old) {old.tearDown();} this.activeScene = scene;scene.setUp();}fire(e) { this.event.fire(e);}addListener(cb, scope) { this.event.add(cb, scope);}}
export default Main;

由上可以看出在 Main 类中我们通过订阅事件提供了场景切换的代码,即通过调用两个场景文件中的 setUp() 方法来完成 3D 场景的切换让我们来看下在 forbiddenCity.js 与 heatMap.js 中是如何进行场景切换的:

setUp() {let g3d = this,dm3d = g3d.dm();super.setUp();util.setSceneLevel('forbiddenCity'); // 清空数据容器dm3d.clear(); // 反序列化 3D 图纸g3d.deserialize('scenes/htdesign/city/故宫/故宫.json', (json, dm, g3d, datas) => {});
}setUp() {let g3d = this,dm3d = g3d.dm();super.setUp();util.setSceneLevel('heatMap'); // 清空数据容器dm3d.clear(); // 反序列化 3D 图纸g3d.deserialize('scenes/htdesign/city/故宫/热力图.json', (json, dm, g3d, datas) => {});
}

以上代码可以看出我们在每次切换场景时都会调用数据容器的 clear() 方法来清空数据然后再调用 g3d.deserialize() 方法反序列化加载新场景图纸,从而完成新旧场景的加载和清空。

投影实现

为增强 3D 场景的立体感,在最新版本的 HT 核心包中新增了场景投影效果配置函数,用户通过调用 enableShadow() 和 disableShadow() 方法可以实现开启关闭 3D 投影效果,此外还可以通过设置 node.s(‘shadow.cast’, false) 对部分不需要投影的模型进行投影关闭处理,投影关键代码:

import util from '../util/util';
const loadScene = {shadow(g3d) { var ssc = function(filter) { var nodes = g3d.dm().toDatas(filter); if (!nodes.length) { return;};nodes.each(function(node) {node.s('shadow.cast', false);});} var nameFilter = function(name) { return function(node) { return node.getDisplayName() === name;}} var typeFilter = function(type) { return function(node) { return node.s('shape3d') === type;}}ssc(nameFilter('路线'));ssc(nameFilter('布景'));ssc(nameFilter('灯光'));ssc(typeFilter('models/医疗/阴影_1.json'));ssc(typeFilter('models/医疗/地面.json'));ssc(typeFilter('models/htdesign/Identification/point/riangle_01.json')) // 为了编组用的 boxssc(typeFilter('box')); if (util.getSceneLevel() === 'forbiddenCity') {g3d.enableShadow({ // 投影 x 轴角度degreeX: 55, // 投影 z 轴角度degreeZ: -35, // low / medium / high / ultra / 4096数值quality: 4096, // 深度浮点偏差补足bias: -0.0003, // none / hard / softtype: 'soft', // type 为 hard / soft 时,补充的边缘厚度,用来提供更柔和的边缘radius: 1.0, // 阴影强度, 1 为黑色intensity: 0.45 });g3d.iv();}}
}
export default loadScene

动画实现

飞鸟动画

飞鸟动画可以拆分为两个步骤:1.飞鸟沿固定路线环绕故宫的飞行动作以及上下位置变化动作,2.飞鸟自身的翅膀扇动动作。我们使用 HT 自带的 ht.Default.startAnim 函数让飞鸟模型沿着三维空间管道做周期运动,在动画中定义了一个变量 count 每次动画都递增,通过 Math.cos(count % 36 * 10 * Math.PI / 180) 函数使值在 1 和 -1 之间做周期变化,配合 setRotationZ() 方法改变翅膀在 3D 拓扑中沿 z 轴的旋转角度从而达到飞鸟翅膀上下扇动,关键代码如下:

// 飞鸟动画
flyerAnim(g3d) {const dm3d = g3d.dm();let polyline = dm3d.getDataByTag('polyline');let flyers = dm3d.getDataByTag('flyers');let count = 0;let radomArr = [this.random(20, 80), this.random(30, 100), this.random(10, 60), this.random(10, 50), this.random(5, 20), this.random(20, 70)]; if (polyline) {let anim = { // 动画周期毫秒数duration: 40000,easing: function(t) { return t;},action: (v, t) => { if (util.getSceneLevel() !== 'heatMap' && polyline) {let length = g3d.getLineLength(polyline); // 获取三维空间管道坐标if (length) {let offset = g3d.getLineOffset(polyline, length * v),point = offset.point,tangent = offset.tangent,px = point.x,py = point.y,pz = point.z,tx = tangent.x,ty = tangent.y,tz = tangent.z;flyers.eachChild((bird, index) => {let ty = bird.getTag().split('_')[1];let positionZ = pz + index * 50 + radomArr[index] / 3,positionX = px + (index - 3) * 50 + radomArr[index] / 3,positionY = py + radomArr[index] / 5; if (index > 2) positionZ = pz - (index - 6) * 50 + radomArr[index] / 3; // 设置飞鸟翅膀扇动动画const pos = count + index,pos2 = count - index * 6; if (pos2 > 0) { if (!bird._posId) bird._posId = pos2;bird._posId++; if (bird._posId > index * 100 + 500 && bird._posId < index * 100 + 600) {bird.eachChild((child) => { if (child.getTag() === 'wingLeft') {child.setRotationZ(0);} else if (child.getTag() === 'wingRight') {child.setRotationZ(0);}}); if (bird._posId === index * 100 + 599) bird._posId = 1;} else {bird.eachChild((child) => { if (child.getTag() === 'wingLeft') {child.setRotationZ(child.r3()[2] + Math.cos(bird._posId % 36 * 10 * Math.PI / 180) * 4 * 0.03);} else if (child.getTag() === 'wingRight') {child.setRotationZ(child.r3()[2] - Math.cos(bird._posId % 36 * 10 * Math.PI / 180) * 4 * 0.03);}});}} // 设置飞鸟飞行轨道动画bird.p3(positionX + radomArr[index] * v, positionY + radomArr[index] * v + Math.cos(count % 36 * 10 * Math.PI / 180) * ty * 5, positionZ + radomArr[index] * v); // 设置飞鸟朝向位置bird.lookAt([positionX + radomArr[index] * v + tx, positionY + ty + radomArr[index] * v, positionZ + radomArr[index] * v + tz]);})count++;}}},finishFunc: function() { // 继续执行飞鸟管道动画this.birdAnim = ht.Default.startAnim(anim);}}; if (util.getSceneLevel() === 'forbiddenCity') { // 执行飞鸟管道动画this.birdAnim = ht.Default.startAnim(anim);}}
}

鸟瞰漫游动画

在飞鸟动画实现的前提下,接下来我们可以进一步以飞鸟模型为中心来生成鸟瞰漫游动画。首先使用 ht.Default.startAnim 函数实时调用飞鸟所在位置,通过 setEye() 和 setCenter() 方法动态设置场景的中心点和相机位置,以此达到从飞鸟的视角俯瞰整个故宫场景的动画效果。关键代码如下:

// 鸟瞰漫游动画
roamingAnim() {const g3d = this.g3d;let flyers = g3d.dm().getDataByTag('flyers');let anim = {duration: 60000, // 动画周期毫秒数easing: function (t) { return t * t;},action: function (v, t) {let flyersP = flyers.p3();let px = flyersP[0];let py = flyersP[1];let pz = flyersP[2];g3d.setEye(px, py + 50, pz - 400);g3d.setCenter(px, py, pz);}} this.roaming = ht.Default.startAnim(anim);
}

景深动画

在HT for Web 中为 3D 组件提供了 enablePostProcessing() 方法,使用者可以通过调用该方法手动开启 3D 场景的景深模糊效果,另外还可以通过设置 aperture 属性改变景深模糊度,在本示例中通过动态改变 aperture 属性形成淡入淡出效果以减少场景切换时的突兀感,关键代码如下:

// 景深动画
depthAnim(g3d, x = 0) {let dof = g3d.getPostProcessingModule('Dof'); // 景深开启g3d.enablePostProcessing('Dof', true); return new Promise((resolve, reject) => {let anim = {duration: 1000,easing: (t) => { return t * t;},action: (v, t) => { // 动态设置景深阈值dof.aperture = x - v * 0.02if (v == 1) resolve('end');}}ht.Default.startAnim(anim);})
}

主要功能

人流量热力图

热力图以特殊高亮的形式显示游客所在的地理区域的图示,可以非常直观的展示人流量密度信息。本示例中使用 HT 封装的 ht.thermodynamic.Thermodynamic3d() 方法动态生成热力图,关键代码如下:

createHeatMap(heatMapName, num) {const g3d = this.g3d;const dm3d = g3d.dm();let room = dm3d.getDataByTag(heatMapName); // 获取要生成热力图的矩形区域let heatRect = room.getRect();let Vector3 = ht.Math.Vector3;let tall = 30 let {x,y,width,height} = heatRect; if (width === 0 || height === 0) return let templateList = []; // 在热力图区域随机生成 num 个热力点位for (let index = 0; index < num; index++) {templateList.push({position: {x: this.random(0, heatRect.width),y: this.random(0, heatRect.height),z: tall},temperature: {value: 30 + this.random(0, 20),radius: 90 },})} // 热力图初始化let thd = window.thd = new ht.thermodynamic.Thermodynamic3d(g3d, {box: new Vector3(width, height, tall),min: 15,max: 55,interval: 200,remainMax: false,opacity: 0.1,colorStopFn: function (v, step) { return v * step * step},gradient: { 0: 'rgba(0,162,255,0.14)', 0.2: 'rgba(48,255,183,0.3)', 0.4: 'rgba(255,245,48,0.5)', 0.6: 'rgba(255,73,18,0.9)', 0.8: 'rgba(217,22,0,0.95)', 1: 'rgb(179,0,0)',}});thd.setData(templateList); // 创建热力图let node = thd.createThermodynamicNode(2, 2, 50);node.setAnchorElevation(0);node.setTag('test');node.p3(room.p3());node.s({ '3d.selectable': false, '3d.movable': false, 'wf.visible': false, 'shape3d.transparent': true,});dm3d.add(node);
}

这里简单的描述下热力图生成步骤:1.首先确定热力图生成区域,在该区域内获取传感器位置和热力信息,并将这些信息存储在 templateList 数组中。2.将数组传入 Thermodynamic3d() 方法中并配置渐变颜色、透明度等相关信息生成热力图渲染数据。3.使用 createThermodynamicNode() 方法按照热力图渲染数据创建热力图。4.将热力图添加到数据容器中。

视频监控

我们通过 addInteractorListener 交互监听器为场景中摄像头模型绑定点击事件,每个摄像头都对应一个监控视频画面,通过点击弹出或关闭,并对窗口中显示的监控画面数量进行了限制,不得超过 4 个否则将不会继续弹出监控画面,避免显示多个画面造成场景遮挡,关键代码如下:

videoVisible(videoName) {let g2d = this.g2d,dm2d = g2d.dm(); // 当前选中监控画面const video = dm2d.getDataByTag(videoName); if (video) {const videoList = video.getParent();const videoRect = video.getRect();const visible = g2d.isVisible(video); if (visible) { // 隐藏选中监控画面,并重新排列监控画面this.hideVideo(videoList, video, videoRect);} else { // 显示选中监控画面,并重新排列监控画面let showVideos = [];videoList.eachChild(child => {g2d.isVisible(child) && child !== video && showVideos.push(child)}) if (showVideos.length < 5) {video.s('2d.visible', true);video.setY(util.getVideoListRect().y + (videoRect.height + 5) * showVideos.length);}}}
}hideVideo(parent, video, videoRect) {parent.eachChild(node => {const nodeRect = node.getRect(); if (nodeRect.y > videoRect.y) {node.setY(nodeRect.y - nodeRect.height)}})video.s('2d.visible', false)
}

总结

现如今,伴随国民经济的持续高速增长,旅游行业迎来了健康发展的阶段,各大景区每年接待的游客人数都在不断增长,如果不对人流量进行控制的话将会出现许多隐患。本次示例效果均采用 HT 提供的 api 进行代码开发,旨在定制一套以人流量监测为中心的集监控与防患应急于一体的景点 3D 实时监控系统,也欢迎对 HT 感兴趣的伙伴给我留言,或者直接访问官网查询相关的资料。

基于 HTML5 WebGL 的故宫人流量动态监控系统相关推荐

  1. 基于 HTML5 WebGL 的 3D 棉花加工监控系统

    前言 现在的棉花加工行业还停留在传统的反应式维护模式当中,当棉花加下厂的设备突然出现故障时,控制程序需要更换.这种情况下,首先需要客户向设备生产厂家请求派出技术人员进行维护,然后生产厂家才能根据情况再 ...

  2. 基于 HTML5 WebGL 的民航客机飞行监控系统

    前言 前些日子出差,在飞机上看到头顶的监控面板,除了播放电视剧和广告之外,还会时不时的切换到一个飞机航行的监控系统,不过整个监控系统让人感到有一点点的简陋,所以我就突发奇想制作了一个采用 HT for ...

  3. 基于 HTML5 WebGL 的加油站 3D 可视化监控

    前言 随着数字化,工业互联网,物联网的发展,我国加油站正向有人值守,无人操作,远程控制的方向发展,传统的人工巡查方式逐渐转变为以自动化控制为主的在线监控方式,即采用数据采集与监控系统 SCADA.SC ...

  4. 基于 HTML5 的计量站三维可视化监控系统 Web 组态工控应用

    得益于 HTML5 WebGL 技术的成熟,从技术上对工控管理的可视化,数据可视化变得简单易行!完成对工控设备的管理效率,资源管理,风险管理等的大幅度提高,同时也对国家工业4.0计划作出有力响应! 如 ...

  5. html5 3d场景设计,基于 HTML5 WebGL 的加油站 3D 可视化监控

    前言 随着数字化,工业互联网,物联网的发展,我国加油站正向有人值守,无人操作,远程控制的方向发展,传统的人工巡查方式逐渐转变为以自动化控制为主的在线监控方式,即采用数据采集与监控系统 SCADA.SC ...

  6. 基于 HTML5 WebGL 的 3D 工控裙房系统

    前言 工业物联网在中国的发展如火如荼,网络基础设施建设,以及工业升级的迫切需要都为工业物联网发展提供了很大的机遇.中国工业物联网企业目前呈现两种发展形式并存状况:一方面是大型通讯.IT企业的布局:一方 ...

  7. 于 HTML5 WebGL 的民航客机飞行监控系统

    前些日子出差,在飞机上看到头顶的监控面板,除了播放电视剧和广告之外,还会时不时的切换到一个飞机航行的监控系统,不过整个监控系统让人感到有一点点的简陋,所以我就突发奇想制作了一个采用 HT for We ...

  8. html5泵站,基于 HTML5 WebGL 的污水处理厂泵站自控系统

    前言 一道残阳铺水中,半江瑟瑟半江红.随着城市建设的迅速发展,每年都有大量新建管网水管通水运行.城市中有大量的排水设备,形成相应的城市排水系统,排水系统由检查井.排水泵站.污水处理厂.雨水口.排放口等 ...

  9. 基于 HTML5 WebGL 的计量站三维可视化监控系统 Web 组态工控应用

    前言 得益于 HTML5 WebGL 技术的成熟,从技术上对工控管理的可视化,数据可视化变得简单易行!完成对工控设备的管理效率,资源管理,风险管理等的大幅度提高,同时也对国家工业4.0计划作出有力响应 ...

最新文章

  1. mqtt+htttp+websocket
  2. python保存两位小数的几种方法,python2保留小数
  3. IIS设置404页面图文教程(选择URL还是文件 )
  4. 10分钟搞定 Java 并发队列好吗?好的
  5. 机器学习理论入门:第二章 经典监督学习算法-决策树
  6. cordova报错:Error: Failed to find ‘ANDROID_HOME‘ environment variable. Try setting setting it manually
  7. Excel-数据分列的多种方法实现
  8. 关闭页面那点事儿...
  9. c读取txt文件_第93天:文件读写
  10. 虚机里的vCenter 迁移
  11. 【元胞自动机】基于matlab六边形网格六方元胞自动机【含Matlab源码 1362期】
  12. DWM1000模块简介
  13. JSP设置IE版本兼容
  14. html样式在ie显示不全,IE下css常见问题总结及解决
  15. OpenNI和Kinect安装中文教程
  16. Linux ubuntu下载vim
  17. 论文翻译-Clicks can be Cheating: Counterfactual Recommendation for Mitigating Clickbait Issue
  18. 哪些人不适合吃枸杞?
  19. Web3.0与数字时尚,该如何落地?
  20. mysql 事务隔离详解_MySQL 中事务、事务隔离级别详解

热门文章

  1. 如何用php农场项目,2020全新亲测php农场游戏源码-金币菇种植理财区块链源码 带商城系统...
  2. Linux流量监控工具 - iftop (最全面的iftop教程),查看服务器的网络情况
  3. outlook2007 配置
  4. Surf函数调节图像方法
  5. matlab 不允许函数定义,MATLAB中此上下文中不允许出现函数定义,急求~
  6. 凸优化笔记6(共轭梯度法)
  7. [a, b]均匀分布方差
  8. PYTHON文件操作(读/写文件)
  9. 线速处理能力的计算方法
  10. 【Auto.js】给手机编一个模拟遥控器