现代工业化的推动在极大加速现代化进程的同时也带来的相应的安全隐患,在传统的可视化监控领域,通常都是基于 Web SCADA 的前端技术来实现 2D 可视化监控,本系统采用 Hightopo 的 HT for Web 产品来构造轻量化的 3D 可视化场景,该 3D 场景从正面展现了一个现代化工厂的现实场景,包括工厂工人的实时位置、电子围栏的范围、现场的安全状况等等,帮助咱们直观的了解当前工厂人员的安全情况。html
本篇文章经过对工厂可视化场景的搭建和模型的加载,人物实时定位代码的实现、电子围栏和轨迹图的实现进行阐述,帮助咱们了解如何经过使用HT实现一个简单的3D电子围栏可视化。前端
如下是项目地址:基于HTML5 WebGL的工业化3D电子围栏、轨迹图node
项目中使用的人物模型是经过 3dMax 建模生成的,该建模工具能够导出 obj 与 mtl 文件,在 HT 中能够经过解析 obj 与 mtl 文件来生成 3d 场景中的摄像头模型。json
项目中场景经过 HT 的 3d 编辑器进行搭建,场景中的模型有些是经过 HT 建模,有些经过 3dMax 建模,以后导入 HT 中。canvas
场景中的电子围栏并非使用3dMax搭建的模型,HT提供了多种基础形体类型供用户建模使用,不一样于传统的3D建模方式,HT的建模核心都是基于API的接口方式, 经过预约义的图元类型和参数接口,进行设置达到三维模型的构建。根据形状,我将电子围栏分红圆柱、长方体和底部为多边形的棱柱。segmentfault
如下是我绘制电子围栏的相关伪代码:数组
G.makeShapes = function (data, typeName, color, lastColor, g3dDm) { //data是包含电子围栏图形信息的json对象数组 let shapes = data; for (let i = 0; i < shapes.length; i++) { let shape = shapes[i]; let type = Number(shape['type']); let x = Number(shape['x']); let y = Number(shape['y']); let z = Number(shape['z']); let width = Number(shape['width']); let height = Number(shape['height']); let tall = Number(shape['tall']); let radius = Number(shape['radius']); let vertexX = shape['vertexX']; let vertexY = shape['vertexY']; let nodePoints = []; let p3 = []; let s3 = []; let centerX = 0; let centerY = 0; let centerZ = 0; let node = new ht.Node(); node.setTag(typeName + i); switch (type) { //第一种形状:圆柱 case 1: p3 = [-x, tall / 2, -y]; s3 = [radius, tall, radius]; //定义电子围栏样式 node.s({ "shape3d": "cylinder", "shape3d.color": color, "shape3d.transparent": true, "shape3d.reverse.color": color, "shape3d.top.color": color, "shape3d.top.visible": false, "shape3d.bottom.color": color, "shape3d.from.color": color, "shape3d.to.color": color }); node.p3(p3); //设置三维坐标 node.s3(s3); //设置形状信息 break; //第二种形状:长方体 case 2: centerX = x - width / 2; centerY = y - height / 2; centerZ = z + tall / 2; p3 = [-Number(centerX) - width, Number(centerZ), -Number(centerY) - height]; s3 = [width, tall, height]; node.s({ "all.color": color, "all.reverse.color": color, "top.visible": false, "all.transparent": true }); node.p3(p3); node.s3(s3); break; //第三种形状:底部为不规则形状的等高体 case 3: let segments = []; for (let i = 0; i < vertexX.length; i++) { let x = -vertexX[i]; let y = -vertexY[i]; let newPoint = { x: x, y: y }; nodePoints.push(newPoint); //1: moveTo,占用1个点信息,表明一个新路径的起点 if (i === 0) { segments.push(1); } else { //2: lineTo,占用1个点信息,表明从上次最后点链接到该点 segments.push(2); if (i === vertexX.length - 1) { //5: closePath,不占用点信息,表明本次路径绘制结束,并闭合到路径的起始点 segments.push(5); } } } node = new ht.Shape(); node.setTag(typeName + i); node.s({ 'shape.background': lastColor, 'shape.border.width': 10, 'shape.border.color': lastColor, 'all.color': lastColor, "all.transparent": true, 'all.opacity': 0.3, }); p3 = [nodePoints[0]['x'], tall / 2, nodePoints[0]['y']]; node.p3(p3); node.setTall(tall); node.setThickness(5); node.setPoints(nodePoints); //node设置点集位置信息 node.setSegments(segments); //node设置点集链接规则 break; } g3dDm.add(node); } }
考虑到电子围栏在某些状况下可能会影响到对人物位置的观察,设置了隐藏电子围栏的功能。在HT中用户能够自定义设置标签Tag做为模型惟一的标识,我将全部的电子围栏模型的标签前缀都统一而且保存在fenceName中,须要隐藏的时候则遍历全部标签名称前缀为fenceName的模型,而且根据模型种类的不一样设置不一样的隐藏方式。安全
如下是相关伪代码:编辑器
g3dDm.each((data) => { if (data.getTag() && data.getTag().substring(0, 4) === fenceName) { if (data.s('all.opacity') === '0') { data.s('all.opacity', '0.3'); } else { data.s('shape3d.visible', true); data.s('all.visible', true); data.s("2d.visible", true); data.s("3d.visible", true); } } });
由于项目使用的是http协议获取数据,所以使用定时器定时刷新人物数据信息,HT有设置节点位置的setPosition3d方法,所以不作过多介绍,可是人物节点的位置的刷新还包括人物的朝向,所以每次人物移动都须要和上次位置进行比对,计算出偏移的角度。工具
相关伪代码以下:
// 刷新数据的人物结点与原来的人物节点标签相同,则存在作位置更新 if (realInfoData.tagId === tag.getTag()) { //计算位置朝向偏移参数 let angleNumber = Math.atan2(((-p3[2]) - (-tag.p3()[2])), ((-p3[0]) - (-tag.p3()[0]))); //若是在原地就不转向,判断人物在平面位置是否发生变化 if (p3[0] !== tag.p3()[0] || p3[2] !== tag.p3()[2]) { if (angleNumber > 0) { angleNumber = Math.PI - angleNumber; } else { angleNumber = -Math.PI - angleNumber; } //设置人物朝向 tag.setRotation3d(0, angleNumber + Math.PI / 2, 0); } //设置人物位置 tag.p3(p3); }
当人物触发警报时,有2种方式同时提醒系统使用者。一是人物头上的面板颜色发生改变,而且显示报警信息。
相关代码以下:
switch(obj.alarmType){ case null: if(panel){//无警报 panel.a('alarmContent',''); panel.a('bg','rgba(6,13,36,0.80)'); } break; case '0': panel.a('alarmContent','进入围栏'); panel.a('bg','rgb(212,0,0)'); break; case '1': panel.a('alarmContent','SOS'); panel.a('bg','rgb(212,0,0)'); break; case '2': panel.a('alarmContent',''); //离开围栏 panel.a('bg','rgba(6,13,36,0.80)'); break; case '3': panel.a('alarmContent','长时间未动'); panel.a('bg','rgb(212,0,0)'); break; }
二是页面的右侧面板会增长警报信息。
相关代码以下:
data.a('text', info); list.dm().add(data);
在发生警报后,须要根据人物的轨迹图回溯发生警报的前因后果。若是使用根据点集每走一步就绘制一个canvas脚步节点的方式去重现轨迹,很容易形成节点绘制过多,页面卡顿的状况,所以我使用一整条管道的方式代替一我的物的全部脚步节点,使用管道的好处是,每一个人物的轨迹图从开始到结束只有一个管道的图元信息,所以对页面的渲染更加友好和流畅。
生成管道轨迹的代码以下:
//生成轨迹 this.ployLines[i] = new ht.Polyline(); this.ployLines[i].setParent(node); this.points[i] = []; this.points[i].push({ x: p3[0], y: p3[2], e: p3[1] -50 }); this.ployLines[i].setPoints(this.points[i]); this.ployLines[i].s({ 'shape.border.color': 'red' }); g3dDm.add(this.ployLines[i]);
人物前进一步,则往管道的点集中推动一个点的坐标,同时绘制新的管道部分。同理,人物后退一步,则管道的点集中推出当前最后一个点的坐标,同时管道失去最后两点链接的部分。另外我经过使用定时器,对轨迹图的前进和后退分别作了快进和快退的处理。如下为轨迹图的运行效果: