HTML5WebGL实现的3D机房示例-
结果图
ht.Default.def('Editor.Server', Object, {//首先个参数为类名,要是为字符串,主动注册到HT的classMap中;第二个参数为此类要继承的父类;第三个参数为办法和变量的声明 addToDataModel: function(dm) { //将节点增加进数据容器 dm.add(this._node);// ht 中的预定义函数,将节点通过 add 办法增加进数据容器中 }, setHost: function() { //设定吸附 this._node.setHost.apply(this._node, arguments); }, s3: function() {//设定节点的大小 this._node.s3.apply(this._node, arguments); }, setElevation: function() {//控制Node图元核心位置所在3D坐标系的y轴位置 this._node.setElevation.apply(this._node, arguments); } });
新建 Editor.Server 类
var S = E.Server = function(obj) {//办事器组件 var color = obj.color, frontImg = obj.frontImg; var node = this._node = new ht.Node();//新建节点 node.s({//设定节点的样式 s 为 setStyle 的缩写 'all.color': color,//设定节点六面的色彩 'front.image': frontImg //设定节点正面的图片 }); };
这样我在需要新建办事器组件的位置直接 new 一个新的办事器组件对象即可,而且能够直接调取我们上面声明的 setHost 等函数,很快我们就会用上。
接下来新建 Editor.Cabinet 机柜类 ,办法跟上面 Editor.Server 类的定义办法差未几:
ht.Default.def('Editor.Cabinet', Object, { addToDataModel: function(dm) { dm.add(this._door); dm.add(this._node); this._serverList.forEach(function(s) { s.addToDataModel(dm); }); }, p3: function() { this._node.p3.apply(this._node, arguments);//设定节点的 3d 坐标 } });
新建 Editor.Cabinet 类
var C = E.Cabinet = function(obj) { var color = obj.color, doorFrontImg = obj.doorFrontImg, doorBackImg = obj.doorBackImg, s3 = obj.s3; var node = this._node = new ht.Node(); // 柜身 node.s3(s3);//设定节点的大小 为 setSize3d node.a('cabinet', this);//自定义 cabinet 属性 node.s({//设定节点的样式 为 setStyle 'all.color': color,//设定节点六面的色彩 'front.visible': false//设定节点前面可否可见 }); if (Math.random() > 0.5) { node.addStyleIcon('alarm', {//向节点上增加 icon 图标 names: ['icon 温度计'],//包括多个字符串的数组,每个字符串对应一张图片或矢量(通过ht.Default.setImage注册) face: 'top',//默许值为front,图标在3D下的朝向,可取值left|right|top|bottom|front|back|center position: 17,//指定icons的位置 autorotate: 'y',//默许值为false,图标在3D下可否主动朝向眼睛的标的目的 t3: [0, 16, 0],//默许值为undefined,图标在3D下的偏移,格局为[x,y,z] width: 37,//指定每个icon的宽度,默许依据注册图片时的宽度 height: 32,//指定每个icon的高度,默许依据注册图片时的高度 textureScale: 4,//默许值为2,该值代表内存现实生成贴图的倍数,不宜设定过大不然影响机能 visible: { func: function() { return !!E.alarmVisible; }}//表示该组图片可否显示 }); } var door = this._door = new ht.DoorWindow();//柜门 door.setWidth(s3[0]);//置图元在3D拓扑中的x轴标的目的的长度 door.setHeight(1);//设定图元在3D拓扑中的z轴长度 door.setTall(s3[1]);//控制Node图元在y轴的长度 door.setElevation(0);//设定图元核心在3D坐标系中的y坐标 door.setY(s3[2] * 0.5);//设定节点在 y 轴的位置 door.setHost(node);//设定吸附 door.s({//设定节点样式 setStyle 'all.color': color,//设定节点六面色彩 'front.image': doorFrontImg,//设定节点正面图片 'front.transparent': true,//设定节点正面可否透亮 'back.image': doorBackImg,//设定节点后面的图片 'back.uv': [1,0, 1,1, 0,1, 0,0],//自定义节点背面uv贴图,为空采纳默许值[0,0, 0,1, 1,1, 1,0] 'dw.axis': 'right'//设定DoorWindow图元展开和关闭操纵的扭转轴,可取值left|right|top|bottom|v|h }); var serverList = this._serverList = []; var max = 6, list = E.randomList(max, Math.floor(Math.random() * (max - 2)) + 2); //global.js 中声明的猎取随机数的函数 var server, h = s3[0] / 4; list.forEach(function(r) { var server = new E.Server({ //办事器组件 color: 'rgb(51,49,49)', frontImg: '办事器 组件精细' }); server.s3(s3[0] - 2, h, s3[2] - 4);//设定节点大小 server.setElevation((r - max * 0.5) * (h + 2));//设定节点核心点在 y 轴的坐标 server.setHost(node);//设定节点的吸附 serverList.push(server);//向 serverList 中增加 server 节点 }); };
上面代码中独一没提到的是 Editor.randomList 函数,这个函数是在 global.js 文件中声明的,声明如下:
var E = window.Editor = { leftWidth: 0, topHeight: 40, randomList: function(max, size) { var list = [], ran; while (list.length < size) { ran = Math.floor(Math.random() * max); if (list.indexOf(ran) >= 0) continue; list.push(ran); } return list; } };
好了,场景中的各个局部的类都新建完成,那我们就该将场景新建起来,然后将这些图元都堆进去!
场景新建
要是熟知的同窗应当晓得,用 HT 新建一个 3D 场景只需要 new 一个 3D 组件,再将通过 addToDOM 函数将这个场景增加进 body 中即可:
var g3d = E.main = new ht.graph3d.Graph3dView(); //3d 场景
main.js 文件中主要做的是在 3D 场景中一些须要的元素,比方墙面,地板,门,空调以及所有的机柜的生成和排放位置,还有非常重要的交互局部。
墙体,地板,门,空协调机柜的新建我就不贴代码出来了,有乐趣的请自行查看代码,这里主要说一下双击机柜以及与机柜有关的任何物体(柜门,办事器设施)则 3D 中 camera 的视线就会挪移到双击的机柜的前方某个位置,并且这个挪移是非常顺滑的,以前武艺不精,致使这个局部想了很久,最后参照 了这个 Demo 的实现办法。
为了能够反复地设定 eye 和 center,将设定这两个参数对应的内容封装为 setEye 和 setCenter 办法,setCenter 办法与 setEye 办法相似,这里不反复赘述:
// 设定眼睛位置 var setEye = function(eye, finish) { if (!eye) return; var e = g3d.getEye().slice(0),//猎取目前 eye 的值 dx = eye[0] - e[0], dy = eye[1] - e[1], dz = eye[2] - e[2]; // 启动 500 毫秒的动画过度 ht.Default.startAnim({ duration: 500, easing: easing,//动画缓动函数 finishFunc: finish || function() {}, //动画完毕后调取的函数 action: function(v, t) {//设定动画v代表通过easing(t)函数运算后的值,t代表目前动画进行的进度[0~1],个别属性变化依据v参数进行 g3d.setEye([ //设定 3D 场景中的 eye 眼睛的值,为一个数组,离别对应 x,y,z 轴的值 e[0] + dx * v, e[1] + dy * v, e[2] + dz * v ]); } }); };
我没有反复声明 setCenter 函数不代表这个函数不重要,恰恰相反,这个函数在“视线”挪移的历程中起到了决议性的作用,上面的 setEye 函数相当于我想走到我的指标位置的前面(至少我定义的时候是这种用法),而 sCenter 的定义则是将我的视线移到了指标的位置(比方我可以站在我此刻的位置看我右后方的物体,也可以走到我右后方去,站在阿谁物体前面看它),这点非常重要,请大家好好品尝一下。
双击事件倒是简略,只有监听 HT 封装好的事件,推断事件类型,并作出响应的行动即可:
g3d.mi(function(e) {//addInteractorListener 事件监听函数 if (e.kind !== 'doubleClickData') //推断事件类型为双击节点 return; var data = e.data, p3; if (data.a('cabinet')) //机身 p3 = data.p3(); else { host = data.getHost(); //猎取点击节点的吸附对象 if (host && host.a('cabinet')) {//要是吸附对象为 cabinet p3 = host.p3(); } } if (!p3) return; setCenter(p3); //设定 center 指标的要移向位置为 cabinet 的位置 setEye([p3[0], 211, p3[2] + 247]); //设定 eye 眼睛要移向的位置 });
顶部导航栏
var form = E.top = new ht.widget.FormPane(); //顶部 表单组件 form.setRowHeight(E.topHeight);//设定行高 form.setVGap(-E.topHeight);//设定表单组件水平间距 设定为行高的负值则可以使多行处于统一行 form.setVPadding(0);//设定表单顶部和顶部与组件内容的间距 form.addRow([null, {//向表单中增加一行组件,首先个参数为元素数组,元素可为字符串、json格局描述的组件参数信息、html元素或者为null image: { icon: './symbols/inputBG.json', stretch: 'centerUniform' } }], [40, 260]);//第二个参数为每个元素宽度信息数组,宽度值大于1代表牢固绝对值,小于等于1代表相对值,也可为80+0.3的组合 form.addRow([null, null, { id: 'searchInput', textField: {} }, { element: '机房可视化治理系统', color: 'white', font: '18px arial, sans-serif' }, null, { button: { // label: '视图切换', icon: './symbols/viewChange.json', background: null, selectBackground: 'rgb(128,128,128)', borderColor: 'rgba(0, 0, 0, 0)', onClicked: function() { E.focusTo(); } } }, null, { button: { // label: '告警', icon: './symbols/alarm.json', togglable: true, selected: false, background: null, selectBackground: 'rgb(128,128,128)', borderColor: 'rgba(0, 0, 0, 0)', onClicked: function(e) { E.setAlarmVisible(this.isSelected()); } } }, null], [40, 42, 218, 300, 0.1, 50, 10, 50, 10]);
以上都只是能实现,但是并没有真正地增加进 html 标签中,也就意味着,此刻界面上什么都没有!别忘了在页面加载的时候将 3D 场景增加进 body 中,同时也别忘了将 form 表单增加进 body 中,而且设定窗口大小变化事件时,form 表单也需要实时更新:
window.addEventListener('load', function() { g3d.addToDOM(); //将 3D 场景增加进 body 中 document.body.appendChild(E.top.getView()); //将 form 表单组件底层 p 增加进 body 中 window.addEventListener('resize', function() {//窗口大小变化事件监听 E.top.iv();//更新 form 表单的底层 p }); });
这里注明一下 addToDOM 函数,关于理解 HT 的机制非常重要。HT 的组件个别都会嵌入 BorderPane、SplitView 和 TabView 等容器中运用,而最外层的 HT 组件则需要会员手工将 getView() 返回的底层 p 元素增加到页面的 DOM 元素中,这里需要注意的是,当父容器大小变化时,要是父容器是 BorderPane 和 SplitView 等这些 HT 预定义的容器组件,则 HT 的容器会主动递归调取孩子组件invalidate 函数通知更新。但要是父容器是原生的 html 元素, 则 HT 组件没法获知需要更新,因而最外层的 HT 组件个别需要监听 window 的窗口大小变化事件,调取最外层组件 invalidate 函数进行更新。
为了最外层组件加载添补满窗口的利便性,HT 的所有组件都有 addToDOM 函数,其实现逻辑如下,其中 iv 是 invalidate 的简写:
addToDOM = function(){ var self = this, view = self.getView(), style = view.style; document.body.appendChild(view); //将场景的底层 p 增加进 body 中 style.left = '0';//HT 默许将所有的组件底层p的position设定为absolute style.right = '0'; style.top = '0'; style.bottom = '0'; window.addEventListener('resize', function () { self.iv(); }, false); //窗口大小变化监听事件,通知组件变化更新 }
这样,所有的代码就完毕了,可以本人右键“检查”,network 中可以猎取相对应的 json 文件。