当前位置: 首页 > Web前端 > HTML5

基于HTML5WebGL的3D机房

时间:2023-04-05 01:49:39 HTML5

前言用WebGL渲染的3D机房已经不是什么新鲜事了。顶起来,想了想,最后觉得这个例子最符合我的要求,所以记录下来。效果图http://hightopo.com/demo/3DRo...这个3D机房的demo还不错,比较漂亮,基本的交互也很满意。接下来,让我们看看如何实现它。代码生成定义类首先从index.html中调用的js路径中打开对应的js,在server.js中自定义一个Editor.Server类。它是由HT封装的ht.Default.def函数创建的(注意创建类名Editor.Server前面的编辑??器不能用E代替):ht.Default.def('Editor.Server',Object,{//第一个参数是类名,如果是字符串,会自动注册到classMap中的HT;第二个参数是这个类要继承的父类;第三个参数是方法的声明andvariablesaddToDataModel: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类这个类可以创建一个ht.Node节点,并设置节点的颜色和正面纹理:varS=E.Server=function(obj){//服务器组件varcolor=obj.color,frontImg=obj.frontImg;varnode=this._node=newht.Node();//创建节点node.s({//设置节点的Styles是setStyle的缩写'all.color':color,//设置六个节点的颜色节点的边'front.image':frontImg//设置节点的正面图片});};这样我就可以直接在需要创建服务器组件的位置新建一个服务器组件对象,并且可以直接调用我们上面声明的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类这个类比前面的Editor.Server服务器组件类相对要复杂一些。在这个类中,一个柜体,柜门和柜内的Server组件:对象.s3;varnode=this._node=newht.Node();//柜体node.s3(s3);//设置节点的尺寸为setSize3dnode.a('cabinet',this);//自定义柜体属性node.s({//设置柜体的样式nodetosetStyle'all.color':color,//设置节点六个面的颜色'front.visible':false//设置节点正面是否可见});if(Math.random()>0.5){node.addStyleIcon('alarm',{//给节点添加一个icon图标names:['iconthermometer'],//包含多个字符串的数组,每个字符串对应一张图片或矢量图(通过ht.Default.setImage注册)face:'top',//默认值为front,方位3D中的图标,可能的值有left|right|top|bottom|front|back|centerposition:17,//指定图标的位置autorotate:'y',//默认值为false,iconisin3D是否自动朝向眼睛的方向t3:[0,16,0],//默认值未定义,图标在3D中的偏移量,格式为[x,y,z]width:37,//指定每个图标的宽度,默认以注册图片的宽度为准height:32,//指定每个图标的高度,默认以注册图片的高度为准textureScale:4,//默认值为2,表示实际生成的纹理由内存Multiple,不宜设置太大或影响性能visible:{func:function(){return!!E.alarmVisible;}}//是否显示该组图片});}vardoor=this._door=newht.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);//设置吸附门.s({//设置节点样式setStyle'all.color':color,//设置节点六面颜色'front.image':doorFrontImg,//设置节点正面图像'front.transparent':true,//设置节点正面是否透明'back.image':doorBackImg,//设置节点背面图像node'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});varserverList=this._serverList=[];varmax=6,list=E.randomList(max,Math.floor(Math.random()*(max-2))+2);//global.js中声明的获取随机数函数varserver,h=s3[0]/4;list.forEach(function(r){varserver=newE.Server({//servercomponentcolor:'rgb(51,49,49)',frontImg:'servercomponentfine'});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});};上面代码中唯一没有提到的就是Editor.randomList函数,这个函数在global.js文件中声明如下:varE=window.Editor={leftWidth:0,topHeight:40,randomList:function(max,size){varlist=[],跑;while(list.length=0)继续;list.push(跑);}返回列表;}};好了,场景各个部分的类都创建好了,那么我们就应该创建场景了,然后这些图片元就堆进去了!熟悉场景创建的应该知道,用HT创建一个3D场景,只需要新建一个3D组件,然后通过addToDOM函数把这个场景添加到body中:varg3d=E.main=newht.graph3d.Graph3dView();//3d场景main.js文件主要是做3D场景中一些必要的元素,比如墙,地板,门,空调,还有所有柜子的产生和排放位置,还有很重要的交互我就不贴了创建一些墙壁、地板、门、空调和橱柜的代码。有兴趣的请自行查看代码。这里我主要说一下双击机柜以及与机柜相关的任何对象(机柜门,服务器设备)。在3D中,摄像机的视线会移动到双击柜子前的某个位置,这个移动非常流畅。之前不熟练,所以这部分想了很久,最后参考了这个Demo的实现方法。为了重复设置eye和center,将这两个参数设置对应的内容封装到setEye和setCenter方法中。setCenter方法和setEye方法类似,这里不再赘述://设置眼睛位置varsetEye=function(eye,finish){if(!eye)return;vare=g3d.getEye().slice(0),//获取当前眼睛的值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场景中的值,是一个数组,分别对应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;vardata=e.data,p3;if(data.a('cabinet'))//bodyp3=data.p3();else{host=data.getHost();//获取点击节点的吸附对象if(host&&host.a('cabinet')){//如果吸附对象是cabinetp3=host.p3();}}if(!p3)return;setCenter(p3);//设置中心目标移动到箱体的位置setEye([p3[0],211,p3[2]+247]);//设置眼睛的位置tomoveto});当我第一次在顶部导航栏看到这个例子的时候,我就在想,这家伙厉害了,我用HT这么久了,HT的ht.widget.Toolbar还没能做出来如此美丽的效果。这本来就是用一个form表单来完成的,厉害了,我傻了。varform=E.top=newht.widget.FormPane();//顶层表单组件form.setRowHeight(E.topHeight);//设置行高form.setVGap(-E.topHeight);//设置表单组件如果水平间距设置为行高的负值,多个lines可以在同一行form.setVPadding(0);//设置窗体的顶部和顶部与组件内容的间距form.addRow([null,{//给窗体添加一行组件,第一个参数是一个元素数组,元素可以是字符串,json格式描述的组件参数信息,html元素或者nullimage:{icon:'./symbols/inputBG.json',stretch:'centerUniform'}}],[40,260]);//第二个参数是每个元素的宽度信息数组。width值大于1表示固定的绝对值,小于等于1表示相对值。也可以是80+0.3的组合.addRow([null,null,{id:'searchInput',textField:{}},{element:'机房可视化管理系统',color:'white',font:'18pxarial,sans-serif'},null,{button:{//label:'ViewSwitch',icon:'./symbols/viewChange.json',background:null,selectBackground:'rgb(128,128,128)',borderColor:'rgba(0,0,0,0)',onClicked:function(){E.focusTo();}}},null,{button:{//label:'Alarm',icon:'./symbols/alarm.json',可切换:true,选择:false,背景:null,selectBackground:'rgb(128,128,128)',borderColor:'rgba(0,0,0,0)',onClicked:function(e){E.setAlarmVisible(this.isSelected());}}},空],[40,42,218,300,0.1,50,10,50,10]);上面只能实现,并没有真正加入到html标签中,也就是说,现在界面上什么都没有!不要忘记在页面加载时将3D场景添加到body中,也不要忘记将窗体添加到body中,并且在设置窗口大小变化事件时,窗体也需要实时更新:window.addEventListener('load',function(){g3d.addToDOM();//添加3D场景到bodydocument.body.appendChild(E.top.getView());//添加bottomdiv将表单组件添加到bodywindowaddEventListener('resize',function(){//窗口大小改变事件监听器E.top.iv();//更新表单底层divform});});这里对addToDOM函数进行解释,用于理解HT的机制非常重要的HT组件一般都嵌入在BorderPane、SplitView、TabView等容器中,而最外层的HT组件需要用户手动添加底层返回的div元素getView()到页面的DOM元素。这里需要注意的是,当父容器的尺寸发生变化时,如果父容器是BorderPane、SplitView等HT预定义的容器组件,HT容器会自动递归调用子组件的invalidate函数来通知更新。但是如果父容器是原生的html元素,HT组件无法知道它需要更新,所以最外层的HT组件一般需要监听window的窗口大小变化事件,调用最外层组件的invalidate函数更新。为了方便加载最外层组件填满窗口,HT的所有组件都有addToDOM函数,实现逻辑如下,其中iv是invalidate的简写:addToDOM=function(){varself=this,view=self.getView(),style=view.style;document.body.appendChild(视图);//将场景底层div添加到body中style.left='0';//HT设置所有组件底层div的位置默认为absolutestyle.right='0';style.top='0';style.bottom='0';window.addEventListener('resize',function(){self.iv();},false);//windowsizechange监听事件,通知componentchangeupdate}这样,所有代码就结束了,可以右键“check”,就可以在network中获取对应的json文件了。