2020-12-03

3D网页小实验——将txt配置文本转化为3D陈列室

设计目标:借鉴前辈编程者的经验将简单的配置文本转化为3D场景,并根据配置文件在场景中加入图片和可播放的视频,最终形成可浏览的3D陈列室。

一、使用效果

1、txt配置文件:

(博客园的富文本编辑器会改变txt文本的排版,所以用图片方式呈现文本)

第一行表示陈列室的每一层前后最多有5个房间,左右最多有8个房间,接下来是第一层的地图:"0"表示普通房间,"+、-、|"表示连接房间的通道,"#"表示地面有洞的房间可用来连接下一层,"^"表示顶部有洞的房间用来连接上一层。"//source"后面是本层的资源,用竖线分隔的参数依次表示前后位置、左右位置、资源"贴在"哪面墙上、资源类型、资源url,比如"//source:2|4|z|mp4|big_buck_bunny.mp4"即表示在第二行第四列的房间的z面(前面)贴上url为big_buck_bunny.mp4的mp4视频。再下面则是-1层的地图。

2、显示效果

房间的整体效果如下:

渲染出了配置文件中设置的房间布局,可以通过修改代码替换默认的草地和线框纹理。场景默认使用Babylon.js的自由相机进行控制,按"v"键可以启用fps式控制,鼠标移动直接控制视角,wasd控制前后左右,c和空格控制升降,同时启用相机与墙壁的碰撞检测阻止穿墙,再次按v键则可关闭fps控制。

进入第二层中间的房间可以看到房间两侧的视频,点击即可播放:

进入第二层右侧的房间,可以看到相邻的小房间融合为一个大房间:

可以通过https://ljzc002.github.io/Txt2room/HTML/PAGE/room.html查看在线实例,代码没有进行编译可以直接在线调试,在https://github.com/ljzc002/ljzc002.github.io/tree/master/Txt2room查看项目代码。

二、代码实现

1、建立房间零件的源网格(master mesh)

为了提高渲染效率,这里并没有为每个房间建立独立的mesh对象,而是将房间拆解为基础的组成零件,对零件建立源网格,然后用WebGL的instance技术批量生成源网格的实例。

以下是生成预制件源网格的方法:

 1 var size_per_u=3;//1纹理坐标长度对应场景的3单位长度 2 var size_per_v=3; 3 var positions=[]; 4 var uvs=[]; 5 var normals=[]; 6 var indices=[]; 7 function initMeshClass() 8 {//plan的基础状态是一个位于原点,面向z轴负方向的平面 9  add_plan2({x:-4.5,y:4.5,z:0},{x:1.5,y:4.5,z:0},{x:1.5,y:1.5,z:0},{x:-4.5,y:1.5,z:0},0);10  add_plan2({x:1.5,y:4.5,z:0},{x:4.5,y:4.5,z:0},{x:4.5,y:-1.5,z:0},{x:1.5,y:-1.5,z:0},4,6/size_per_u);11  add_plan2({x:-1.5,y:-1.5,z:0},{x:4.5,y:-1.5,z:0},{x:4.5,y:-4.5,z:0},{x:-1.5,y:-4.5,z:0},8,3/size_per_u,6/size_per_v);12  add_plan2({x:-4.5,y:1.5,z:0},{x:-1.5,y:1.5,z:0},{x:-1.5,y:-4.5,z:0},{x:-4.5,y:-4.5,z:0},12,0,3/size_per_v);13  var mesh=vertexData2Mesh(positions, indices, normals, uvs,"class_hole",mat_grass);14  mesh.setEnabled(false);//令源网格不显示15  // 很奇怪如果不对长通道设置mesh.setEnabled(false);则实例无法正常显示,但其他类的实例则没有这种问题。16  //mesh.setEnabled(true);//默认就是这个17  obj_meshclass["hole"]=mesh;//带有洞的墙壁18 19  positions=[];//新建式清空,理论上不影响引用的数据20  uvs=[];21  normals=[];22  indices=[];23  add_plan2({x:-4.5,y:4.5,z:0},{x:4.5,y:4.5,z:0},{x:4.5,y:-4.5,z:0},{x:-4.5,y:-4.5,z:0},0);24  var mesh=vertexData2Mesh(positions, indices, normals, uvs,"class_wall",mat_grass);25  mesh.setEnabled(false);26  obj_meshclass["wall"]=mesh;//墙壁27 28  positions=[];29  uvs=[];30  normals=[];31  indices=[];32  add_plan2({x:-1.5,y:1.5,z:0},{x:1.5,y:1.5,z:0},{x:1.5,y:-1.5,z:0},{x:-1.5,y:-1.5,z:0},0);33  var mesh=vertexData2Mesh(positions, indices, normals, uvs,"class_smallwall",mat_grass);34  mesh.setEnabled(false);35  obj_meshclass["smallwall"]=mesh;//小块墙壁36 37  positions=[];38  uvs=[];39  normals=[];40  indices=[];41  add_plan2({x:-1.5,y:1.5,z:-4.5},{x:-1.5,y:1.5,z:4.5},{x:1.5,y:1.5,z:4.5},{x:1.5,y:1.5,z:-4.5},0);42  add_plan2({x:1.5,y:1.5,z:-4.5},{x:1.5,y:1.5,z:4.5},{x:1.5,y:-1.5,z:4.5},{x:1.5,y:-1.5,z:-4.5},4);43  add_plan2({x:1.5,y:-1.5,z:-4.5},{x:1.5,y:-1.5,z:4.5},{x:-1.5,y:-1.5,z:4.5},{x:-1.5,y:-1.5,z:-4.5},8);44  add_plan2({x:-1.5,y:-1.5,z:-4.5},{x:-1.5,y:-1.5,z:4.5},{x:-1.5,y:1.5,z:4.5},{x:-1.5,y:1.5,z:-4.5},12);45  var mesh=vertexData2Mesh(positions, indices, normals, uvs,"class_channel",mat_frame);46  //mesh.setEnabled(false);47  // 很奇怪如果不对长通道设置mesh.setEnabled(false);则实例无法正常显示,但其他类的实例则没有这种问题。48  mesh.setEnabled(false);49  obj_meshclass["channel"]=mesh;//长通道50 51  positions=[];52  uvs=[];53  normals=[];54  indices=[];55  add_plan2({x:-1.5,y:1.5,z:1.5},{x:-1.5,y:1.5,z:4.5},{x:1.5,y:1.5,z:4.5},{x:1.5,y:1.5,z:1.5},0);56  add_plan2({x:1.5,y:1.5,z:1.5},{x:1.5,y:1.5,z:4.5},{x:1.5,y:-1.5,z:4.5},{x:1.5,y:-1.5,z:1.5},4);57  add_plan2({x:1.5,y:-1.5,z:1.5},{x:1.5,y:-1.5,z:4.5},{x:-1.5,y:-1.5,z:4.5},{x:-1.5,y:-1.5,z:1.5},8);58  add_plan2({x:-1.5,y:-1.5,z:1.5},{x:-1.5,y:-1.5,z:4.5},{x:-1.5,y:1.5,z:4.5},{x:-1.5,y:1.5,z:1.5},12);59  var mesh=vertexData2Mesh(positions, indices, normals, uvs,"class_shortchannel",mat_grass);60  mesh.setEnabled(false);61  obj_meshclass["shortchannel"]=mesh;//短通道62 }

其中add_plan2方法用来根据四个给定的顶点生成平整的四边形顶点数据:

 1 //平面四个顶点的坐标(从左上角开始顺时针排列),第一个顶点的插入索引,uv纹理的坐标的偏移量 2 function add_plan2(v1,v2,v3,v4,index,offsetu,offsetv) 3 { 4  positions.push(v1.x); 5  positions.push(v1.y); 6  positions.push(v1.z); 7  positions.push(v2.x); 8  positions.push(v2.y); 9  positions.push(v2.z);10  positions.push(v3.x);11  positions.push(v3.y);12  positions.push(v3.z);13  positions.push(v4.x);14  positions.push(v4.y);15  positions.push(v4.z);16  //使用和Babylon.js条带网格相同的顶点顺序17  indices.push(index+3);18  indices.push(index+2);19  indices.push(index);20  indices.push(index+1);21  indices.push(index);22  indices.push(index+2);23  //根据顶点位置计算平整纹理坐标24  //1234对应abcd25  var vab=v3subtract(v2,v1);26  var lab=v3length(vab);27  var vac=v3subtract(v3,v1);28  var lac=v3length(vac);29  var vad=v3subtract(v4,v1);30  var lad=v3length(vad);31 32  var BAC=Math.acos((vab.x*vac.x+vab.y*vac.y+vab.z*vac.z)/(lab*lac));33  var BAD=Math.acos((vab.x*vad.x+vab.y*vad.y+vab.z*vad.z)/(lab*lad));34  if(!offsetu)35  {36   offsetu=0;37  }38  if(!offsetv)39  {40   offsetv=0;41  }42  uvs.push(offsetu);43  uvs.push(offsetv);44  uvs.push(offsetu+lab/size_per_u);45  uvs.push(offsetv);46  uvs.push(offsetu+(lac*Math.cos(BAC)/size_per_u));47  uvs.push(offsetv+(lac*Math.sin(BAC)/size_per_v));48  uvs.push(offsetu+(lad*Math.cos(BAD)/size_per_u));49  uvs.push(offsetv+(lad*Math.sin(BAD)/size_per_v));50 }51 function v3subtract(v1,v2)//向量相减52 {53  return {x:(v1.x-v2.x),y:(v1.y-v2.y),z:(v1.z-v2.z)}54 }55 function v3length(v)//计算向量长度56 {57  return Math.pow(v.x*v.x+v.y*v.y+v.z*v.z,0.5)58 }

计算平整纹理坐标使用了向量点乘的性质:vab.x*vac.x+vab.y*vac.y+vab.z*vac.z=vab.vac=lab*lac*cosCAB

这使得我们可以根据三角形三个顶点的坐标计算出其中两个向量的夹角,进而在这两个向量确定的平面中计算出三个顶点的纹理坐标。

可以在https://forum.babylonjs.com/t/which-way-should-i-choose-to-make-a-custom-mesh-from-ribbon/10793查看一些关于为什么要进行纹理平整的讨论。

vertexData2Mesh方法用来将生成的顶点数据转化为网格,

 1 function vertexData2Mesh(positions, indices, normals, uvs,name,material) 2 { 3  var vertexData= new BABYLON.VertexData();//顶点数据对象 4  BABYLON.VertexData.ComputeNormals(positions, indices, normals);//计算法线 5  BABYLON.VertexData._ComputeSides(0, positions, indices, normals, uvs); 6  vertexData.indices = indices.concat();//索引 7  vertexData.positions = positions.concat(); 8  vertexData.normals = normals.concat();//position改变法线也要改变!!!! 9  vertexData.uvs = uvs.concat();10  var mesh=new BABYLON.Mesh(name,scene);11  vertexData.applyToMesh(mesh, true);12  mesh.vertexData=vertexData;13  mesh.material=material;14  mesh.renderingGroupId=2;15  return mesh;16 }

最后mesh.setEnabled(false)用来隐藏源网格。

2、读取配置文本,提取房间信息

 1 var str=newland.importString("06.txt"); 2   //console.log(str); 3   var arr=str.split("\r\n")//限于window操作系统下?? 4   var len=arr.length; 5   for(var i=0;i<len;i++)//对于每一行 6   { 7    var line=arr[i]; 8    if(line.substring(0,2)=="//") 9    {10     var arr2=line.substring(2).split("@");11     var len2=arr2.length;12     for(var j=0;j<len2;j++)13     {14      var obj=arr2[j];15      var arr3=obj.split(":");16      var ptype=arr3[0];17      var pvalue=arr3[1];18      if(ptype=="seg_z")19      {20       seg_z=parseInt(pvalue);21      }22      else if(ptype=="seg_x")23      {24       seg_x=parseInt(pvalue);25      }26      else if(ptype=="floor")//进入了一层27      {28       i=handleFloor(pvalue,arr,i+1);29      }30     }31    }32   }

其中importString是一个读取服务端文本文件的方法,其代码如下:

1 newland.importString=function(url)2 {3  var xhr=new 4  xhr.open("GET",url,false);//第三个参数表示是同步加载5  xhr.send(null);6  var data=xhr.responseText;7  return data;8 }

读入后一行行遍历文本,发现"//floor"则开始处理这一层的房间数据:

 1 function handleFloor(int_floor,arr,index) 2 { 3  var floor=obj_building[int_floor];//在obj_building中保存所有房间信息 4  if(!floor) 5  { 6   obj_building[int_floor]={}; 7   floor=obj_building[int_floor]; 8  } 9  var len=arr.length;10  var count=0;11  //继续读txt文本12  for(var i=index;i<len;i++)13  {14   var line=arr[i];15   count++;16   if(count<=seg_z)17   {18 19    if(!floor[count+""])20    {21     floor[count+""]={}22    }23 24    for(var j=0;j<seg_x;j++)25    {26     if(line[j])27     {28 29      floor[count+""][j+1+""]={type:line[j],arr_source:[]};//这个"数组"都是从一开始的30      //addRoom(count-1,j);//行、列,规划完毕后统一添加渲染31     }32    }33   }34   else35   {36    if(line.substring(0,7)=="//floor")//查找到另一层37    {38     return (index+count-2);39    }40    else if(line.substring(0,8)=="//source")//为这个房间设置资源41    {42     //var arr2=line.split(":")[1].split("|");43     var arr2=line.substring(line.search(":")+1).split("|");44     if(floor[arr2[0]]&&floor[arr2[0]][arr2[1]])45     {46      var arr_source=floor[arr2[0]][arr2[1]].arr_source;//这个房间的资源列表47      var obj={};48      obj.sourceSide=arr2[2];49      obj.sourceType=arr2[3];50      obj.sourceUrl=arr2[4];51      arr_source.push(obj);52     }53 54    }55   }56 57  }58  return (len);//查找到文件末尾59 }

经过以上处理,配置文件中的房间信息都被提取到obj_building中。

3、根据房间信息排列源网格的实例,并放置资源。

代码如下:(有一定冗余)

 1 function handleBuilding() 2 { 3  var len=0; 4  for(var key in obj_building) 5  { 6   len++;//总层数 7  } 8  for(var key in obj_building)//对于每一层 9  { 10   var int_key=parseInt(key); 11   var floor=obj_building[key]; 12   //寻找这一层的上下两层,这里假设obj_building是没有顺序的 13   var int_key_shang=int_key,int_key_xia=int_key; 14   var floor_shang=null,floor_xia=null; 15   //for(var i=int_key;i<) 16   for(var key2 in obj_building) 17   { 18    var int_key2=parseInt(key2); 19    if((int_key2>int_key)&&(int_key_shang==int_key||int_key_shang>int_key2)) 20    { 21     int_key_shang=int_key2; 22     floor_shang=obj_building[key2]; 23    } 24    if((int_key2<int_key)&&(int_key_xia==int_key||int_key_xia<int_key2)) 25    { 26     int_key_shang=int_key2; 27     floor_xia=obj_building[key2]; 28    } 29   } 30   for(var i=1;i<=seg_z;i++)//对于本层的每一行房间 31   { 32    var row=floor[i+""]; 33    if(row)//如果有这一行 34    { 35     for(var j=0;j<=seg_x;j++)//对于本行的每一个房间 36     { 37      var room=row[j+""]; 38      //根据房间的类型不同决定是否要查看其周围的房间 39      if(room) 40      {//@@@@普通房间,要考虑前后左右的四个房间状态,要考虑资源放置 41       if(room.type=="0"||room.type=="#"||room.type=="^") 42       { 43        //room.arr_source=[]; 44        //考虑前面 45        if(floor[i-1+""]&&floor[i-1+""][j+""]) 46        { 47         var room2=floor[i-1+""][j+""]; 48         if(!room2)//如果没有东西,就是普通墙壁 49         { 50          //网格类型,实例名字,位置,姿态 51          drawMesh("wall","wall_z_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3(j*sizex,int_key*sizey,(0.5-i)*sizez) 52           ,new BABYLON.Vector3(0,0,0)) 53         } 54  55         else if(room2.type=="|"||room2.type=="+") 56         { 57          drawMesh("hole","hole_z_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3(j*sizex,int_key*sizey,(0.5-i)*sizez) 58           ,new BABYLON.Vector3(0,0,0)) 59         } 60         else if(room2.type=="0"||room2.type=="#"||room2.type=="^")//旁边也是一个房间则合并房间,不绘制墙壁 61         { 62  63         } 64         else//默认绘制墙壁 65         { 66          drawMesh("wall","wall_z_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3(j*sizex,int_key*sizey,(0.5-i)*sizez) 67           ,new BABYLON.Vector3(0,0,0)) 68         } 69        } 70        else//默认绘制墙壁 71        { 72         drawMesh("wall","wall_z_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3(j*sizex,int_key*sizey,(0.5-i)*sizez) 73          ,new BABYLON.Vector3(0,0,0)) 74        } 75        //后面 76        if(floor[i+1+""]&&floor[i+1+""][j+""]) 77        { 78         var room2=floor[i+1+""][j+""]; 79         if(!room2)//如果没有东西,就是普通墙壁 80         { 81          //网格类型,实例名字,位置,姿态 82          drawMesh("wall","wall_z-_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3(j*sizex,int_key*sizey,(-0.5-i)*sizez) 83           ,new BABYLON.Vector3(0,0,0)) 84         } 85  86         else if(room2.type=="|"||room2.type=="+") 87         { 88          drawMesh("hole","hole_z-_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3(j*sizex,int_key*sizey,(-0.5-i)*sizez) 89           ,new BABYLON.Vector3(0,0,0)) 90         } 91         else if(room2.type=="0"||room2.type=="#"||room2.type=="^")//旁边也是一个房间则合并房间,不绘制墙壁 92         { 93  94         } 95         else//默认绘制墙壁 96         { 97          drawMesh("wall","wall_z-_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3(j*sizex,int_key*sizey,(-0.5-i)*sizez) 98           ,new BABYLON.Vector3(0,0,0)) 99         }100        }101        else//默认绘制墙壁102        {103         drawMesh("wall","wall_z-_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3(j*sizex,int_key*sizey,(-0.5-i)*sizez)104          ,new BABYLON.Vector3(0,0,0))105        }106        //左边107        if(floor[i+""][j-1+""])108        {109         var room2=floor[i+""][j-1+""];110         if(!room2)//如果没有东西,就是普通墙壁111         {112          //网格类型,实例名字,位置,姿态113          drawMesh("wall","wall_x-_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j-0.5)*sizex,int_key*sizey,(-i)*sizez)114           ,new BABYLON.Vector3(0,Math.PI/2,0))115         }116 117         else if(room2.type=="-"||room2.type=="+")118         {119          drawMesh("hole","hole_x-_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j-0.5)*sizex,int_key*sizey,(-i)*sizez)120           ,new BABYLON.Vector3(0,Math.PI/2,0))121         }122         else if(room2.type=="0"||room2.type=="#"||room2.type=="^")//旁边也是一个房间则合并房间,不绘制墙壁123         {124 125         }126         else//默认绘制墙壁127         {128          drawMesh("wall","wall_x-_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j-0.5)*sizex,int_key*sizey,(-i)*sizez)129           ,new BABYLON.Vector3(0,Math.PI/2,0))130         }131        }132        else//默认绘制墙壁133        {134         drawMesh("wall","wall_x-_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j-0.5)*sizex,int_key*sizey,(-i)*sizez)135          ,new BABYLON.Vector3(0,Math.PI/2,0))136        }137        //右边138        if(floor[i+""][j+1+""])139        {140         var room2=floor[i+""][j+1+""];141         if(!room2)//如果没有东西,就是普通墙壁142         {143          //网格类型,实例名字,位置,姿态144          drawMesh("wall","wall_x_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j+0.5)*sizex,int_key*sizey,(-i)*sizez)145           ,new BABYLON.Vector3(0,Math.PI/2,0))146         }147 148         else if(room2.type=="-"||room2.type=="+")149         {150          drawMesh("hole","hole_x_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j+0.5)*sizex,int_key*sizey,(-i)*sizez)151           ,new BABYLON.Vector3(0,Math.PI/2,0))152         }153         else if(room2.type=="0"||room2.type=="#"||room2.type=="^")//旁边也是一个房间则合并房间,不绘制墙壁154         {155 156         }157         else//默认绘制墙壁158         {159          drawMesh("wall","wall_x_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j+0.5)*sizex,int_key*sizey,(-i)*sizez)160           ,new BABYLON.Vector3(0,Math.PI/2,0))161         }162        }163        else//默认绘制墙壁164        {165         drawMesh("wall","wall_x_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j+0.5)*sizex,int_key*sizey,(-i)*sizez)166          ,new BABYLON.Vector3(0,Math.PI/2,0))167        }168        //上面169        if(room.type=="^")170        {171         drawMesh("hole","hole_y_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j)*sizex,(int_key+0.5)*sizey,(-i)*sizez)172          ,new BABYLON.Vector3(Math.PI/2,0,0))173         //还要负责向上连接174         if(floor_shang)175         {176          for(var k=int_key+1;k<int_key_shang;k++)177          {178           drawMesh("channel","channel_^_"+k+"_"+i+"_"+j,new BABYLON.Vector3((j)*sizex,(k)*sizey,(-i)*sizez)179            ,new BABYLON.Vector3(Math.PI/2,0,0))180          }181         }182         //暂时不设置弹射器,使用失重模式183        }184        else185        {186         drawMesh("wall","wall_y_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j)*sizex,(int_key+0.5)*sizey,(-i)*sizez)187          ,new BABYLON.Vector3(Math.PI/2,0,0))188        }189        //下面190        if(room.type=="#")191        {192         drawMesh("hole","hole_y-_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j)*sizex,(int_key-0.5)*sizey,(-i)*sizez)193          ,new BABYLON.Vector3(Math.PI/2,0,0))194        }195        else196        {197         drawMesh("wall","wall_y-_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j)*sizex,(int_key-0.5)*sizey,(-i)*sizez)198          ,new BABYLON.Vector3(Math.PI/2,0,0))199        }200        //翻转方向会影响碰撞检测吗?201        //最后处理资源202       }203       //@@@@表示通道的三种符号,要考虑其前后左右的位置204       else if(room.type=="-"||room.type=="+"||room.type=="|")205       {206        if(room.type=="-")207        {//横向长通道208         drawMesh("channel","channel_-_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j)*sizex,(int_key)*sizey,(-i)*sizez)209          ,new BABYLON.Vector3(0,Math.PI/2,0))210        }211        else if(room.type=="|")212        {//纵向长通道213         drawMesh("channel","channel_|_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j)*sizex,(int_key)*sizey,(-i)*sizez)214          ,new BABYLON.Vector3(0,0,0))215        }216        else217        {//十字连接件218         //考虑前面219         if(floor[i-1+""]&&floor[i-1+""][j+""])220         {221          var room2=floor[i-1+""][j+""];222          if(!room2)//如果没有东西,就是普通墙壁223          {224           //网格类型,实例名字,位置,姿态225           drawMesh("smallwall","smallwall_z_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3(j*sizex,int_key*sizey,(0.5/3-i)*sizez)226            ,new BABYLON.Vector3(0,0,0))227          }228 229          else if(room2.type=="|"||room2.type=="+"||room2.type=="0"||room2.type=="#"||room2.type=="^")230          {//短通道自带位移231           drawMesh("shortchannel","shortchannel_z_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3(j*sizex,int_key*sizey,(-i)*sizez)232            ,new BABYLON.Vector3(0,0,0))233          }234          else//默认绘制小型墙壁235          {236           drawMesh("smallwall","smallwall_z_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3(j*sizex,int_key*sizey,(0.5/3-i)*sizez)237            ,new BABYLON.Vector3(0,0,0))238          }239         }240         else//默认绘制小型墙壁241         {242          drawMesh("smallwall","smallwall_z_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3(j*sizex,int_key*sizey,(0.5/3-i)*sizez)243           ,new BABYLON.Vector3(0,0,0))244         }245         //后面246         if(floor[i+1+""]&&floor[i+1+""][j+""])247         {248          var room2=floor[i+1+""][j+""];249          if(!room2)//如果没有东西,就是普通墙壁250          {251           //网格类型,实例名字,位置,姿态252           drawMesh("smallwall","smallwall_z-_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3(j*sizex,int_key*sizey,(-0.5/3-i)*sizez)253            ,new BABYLON.Vector3(0,0,0))254          }255 256          else if(room2.type=="|"||room2.type=="+"||room2.type=="0"||room2.type=="#"||room2.type=="^")257          {258           drawMesh("shortchannel","shortchannel_z-_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3(j*sizex,int_key*sizey,(-i)*sizez)259            ,new BABYLON.Vector3(0,Math.PI,0))260          }261          else//默认绘制小型墙壁262          {263           drawMesh("smallwall","smallwall_z-_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3(j*sizex,int_key*sizey,(-0.5/3-i)*sizez)264            ,new BABYLON.Vector3(0,0,0))265          }266         }267         else//默认绘制小型墙壁268         {269          drawMesh("smallwall","smallwall_z-_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3(j*sizex,int_key*sizey,(-0.5/3-i)*sizez)270           ,new BABYLON.Vector3(0,0,0))271         }272         //左边273         if(floor[i+""][j-1+""])274         {275          var room2=floor[i+""][j-1+""];276          if(!room2)//如果没有东西,就是小型墙壁277          {278           //网格类型,实例名字,位置,姿态279           drawMesh("smallwall","smallwall_x-_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j-0.5/3)*sizex,int_key*sizey,(-i)*sizez)280            ,new BABYLON.Vector3(0,Math.PI/2,0))281          }282 283          else if(room2.type=="-"||room2.type=="+"||room2.type=="0"||room2.type=="#"||room2.type=="^")284          {285           drawMesh("shortchannel","shortchannel_x-_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j)*sizex,int_key*sizey,(-i)*sizez)286            ,new BABYLON.Vector3(0,-Math.PI/2,0))287          }288          else//默认绘制小型墙壁289          {290           drawMesh("smallwall","smallwall_x-_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j-0.5/3)*sizex,int_key*sizey,(-i)*sizez)291            ,new BABYLON.Vector3(0,Math.PI/2,0))292          }293         }294         else//默认绘制小型墙壁295         {296          drawMesh("smallwall","smallwall_x-_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j-0.5/3)*sizex,int_key*sizey,(-i)*sizez)297           ,new BABYLON.Vector3(0,Math.PI/2,0))298         }299         //右边300         if(floor[i+""][j+1+""])301         {302          var room2=floor[i+""][j+1+""];303          if(!room2)//如果没有东西,就是普通墙壁304          {305           //网格类型,实例名字,位置,姿态306           drawMesh("smallwall","smallwall_x_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j+0.5/3)*sizex,int_key*sizey,(-i)*sizez)307            ,new BABYLON.Vector3(0,Math.PI/2,0))308          }309 310          else if(room2.type=="-"||room2.type=="+"||room2.type=="0"||room2.type=="#"||room2.type=="^")311          {312           drawMesh("shortchannel","shortchannel_x_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j)*sizex,int_key*sizey,(-i)*sizez)313            ,new BABYLON.Vector3(0,Math.PI/2,0))314          }315          else//默认绘制墙壁316          {317           drawMesh("smallwall","smallwall_x_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j+0.5/3)*sizex,int_key*sizey,(-i)*sizez)318            ,new BABYLON.Vector3(0,Math.PI/2,0))319          }320         }321         else//默认绘制墙壁322         {323          drawMesh("smallwall","smallwall_x_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j+0.5/3)*sizex,int_key*sizey,(-i)*sizez)324           ,new BABYLON.Vector3(0,Math.PI/2,0))325         }326         //上面327         drawMesh("smallwall","smallwall_y_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j)*sizex,(int_key+0.5/3)*sizey,(-i)*sizez)328          ,new BABYLON.Vector3(Math.PI/2,0,0))329         //下面330         drawMesh("smallwall","smallwall_y-_"+int_key+"_"+i+"_"+j,new BABYLON.Vector3((j)*sizex,(int_key-0.5/3)*sizey,(-i)*sizez)331          ,new BABYLON.Vector3(Math.PI/2,0,0))332 333        }334       }335       //如果这个房间有资源336       if(room.arr_source)337       {338        var arr_source=room.arr_source;339        var len=arr_source.length;340        for(var k=0;k<len;k++)341        {342         var source=arr_source[k];343         if(source.sourceType=="mp4"||source.sourceType=="jpg"||source.sourceType=="png")344         {345          var mesh_plan=new BABYLON.MeshBuilder.CreatePlane(source.sourceType+"_"+source.sourceSide+"_"+int_key+"_"+i+"_"+j,346           {height:4.5,width:8},scene);//建立一个平面网格用来展示资源347          var pos={x:0,y:0,z:0},rot=new BABYLON.Vector3(0,0,0);348          if(source.sourceSide=="z")//根据资源所在的墙壁不同调整资源网格的位置和姿态349          {350           pos.z=0.4351          }else if(source.sourceSide=="z-")352          {353           pos.z=-0.4;354           rot.y=Math.PI;355          }356          else if(source.sourceSide=="x")357          {358           pos.x=0.4;359           rot.y=-Math.PI/2;360          }361          else if(source.sourceSide=="x-")362          {363           pos.x=-0.4;364           rot.y=Math.PI/2;365          }366          else if(source.sourceSide=="y")367          {368           pos.y=0.4;369           rot.x=Math.PI/2;370          }371          else if(source.sourceSide=="z-")372          {373           pos.y=-0.4;374           rot.x=-Math.PI/2;375          }376          mesh_plan.position=new BABYLON.Vector3((j+pos.x)*sizex,(int_key+pos.y)*sizey,(-i+pos.z)*sizez);377          mesh_plan.rotation=rot;378          mesh_plan.renderingGroupId=2;379          if(source.sourceType=="jpg"||source.sourceType=="png")380          {381           var materialf = new BABYLON.StandardMaterial("mat_"+source.sourceSide+"_"+int_key+"_"+i+"_"+j, scene);382 383           materialf.diffuseTexture = new BABYLON.Texture(source.sourceUrl, scene);384           materialf.diffuseTexture.hasAlpha = false;385           materialf.backFaceCulling = true;386           materialf.freeze();387           mesh_plan.material =materialf;388          }389          else if(source.sourceType=="mp4")390          {391           var mat = new BABYLON.StandardMaterial("mat_"+source.sourceSide+"_"+int_key+"_"+i+"_"+j, scene);392           //从Chrome 66开始为了避免标签产生随机噪音禁止没有交互前使用js播放视频,所以后面要监听点击启动播放393           var videoTexture = new BABYLON.VideoTexture("video_"+source.sourceSide+"_"+int_key+"_"+i+"_"+j, [source.sourceUrl], scene, true, false);394           //videoTexture.video.autoplay=false;//这两个设置395           //videoTexture.video.muted=true;不起作用396           mat.diffuseTexture = videoTexture;//Babylon.js视频纹理397           mat.emissiveColor=new BABYLON.Color3(1,1,1);398           //监听到交互需求399           // videoTexture.onUserActionRequestedObservable.add(() => {400           //  scene.onPointerDown = function (evt) {401           //   if(evt.pickInfo.pickedMesh == mesh_plan)402           //   {403           //    if(videoTexture.video.paused)404           //    {405           //     videoTexture.video.play();406           //    }407           //    else408           //    {409           //     videoTexture.video.pause();410           //    }411           //   }412           //413           //  }414           // });415           //mat.emissiveTexture= videoTexture;416           mesh_plan.material =mat;417           obj_videos[mesh_plan.name]=videoTexture;418           if(false)419           {420            scene.onPointerDown = function (evt) {//这个evt是dom的,不会有pickInfo!!421             if(evt.pickInfo&&(evt.pickInfo.pickedMesh == mesh_plan))422             {423              if(videoTexture.video.paused)424              {425               videoTexture.video.play();426              }427              else428              {429               videoTexture.video.pause();430              }431             }432            }433           }434 435          }436         }437        }438       }439 440      }441     }442    }443   }444  }445 }

drawMesh方法用来在指定位置生成指定源网格的实例:

1 function drawMesh(type,name,pos,rot)2  {3   var instance=obj_meshclass[type].createInstance(name);4   instance.position=pos;5   instance.rotation=rot;6  }

在完成零件组装后,再根据资源信息向设定的位置添加资源。

4、运动控制与碰撞检测

监听操作者的鼠标和键盘操作:

 1 var node_temp; 2 function InitMouse() 3 { 4  canvas.addEventListener("blur",function(evt){//监听失去焦点 5   releaseKeyStateOut(); 6  }) 7  canvas.addEventListener("focus",function(evt){//改为监听获得焦点,因为调试失去焦点时事件的先后顺序不好说 8   releaseKeyStateIn(); 9  })10 11  //scene.onPointerPick=onMouseClick;//如果不attachControl onPointerPick不会被触发,并且onPointerPick必须pick到mesh上才会被触发12  canvas.addEventListener("click", function(evt) {//这个监听也会在点击GUI按钮时触发!!13   onMouseClick(evt);//14  }, false);15  canvas.addEventListener("dblclick", function(evt) {//是否要用到鼠标双击??16   onMouseDblClick(evt);//17  }, false);18  scene.onPointerMove=onMouseMove;//Babylon.js的事件监听属性19  scene.onPointerDown=onMouseDown;20  scene.onPointerUp=onMouseUp;21  scene.onKeyDown=onKeyDown;22  scene.onKeyUp=onKeyUp;23  node_temp=new BABYLON.TransformNode("node_temp",scene);//用来提取相机的姿态矩阵24  node_temp.rotation=camera0.rotation;25 }

鼠标点击控制视频播放:

 1 function onMouseDblClick(evt) 2 { 3  var pickInfo = scene.pick(scene.pointerX, scene.pointerY, null, false, camera0); 4  if(pickInfo.hit) 5  { 6   var mesh = pickInfo.pickedMesh; 7   if(mesh.name.split("_")[0]=="mp4")//重放视频 8   { 9    if(obj_videos[mesh.name])10    {11     var videoTexture=obj_videos[mesh.name];12 13      videoTexture.video.currentTime =0;14 15    }16   }17  }18 }19 function onMouseClick(evt)20 {21  var pickInfo = scene.pick(scene.pointerX, scene.pointerY, null, false, camera0);22  if(pickInfo.hit)23  {24   var mesh = pickInfo.pickedMesh;25   if(mesh.name.split("_")[0]=="mp4")//启停视频26   {27    if(obj_videos[mesh.name])28    {29     var videoTexture=obj_videos[mesh.name];30     if(videoTexture.video.paused)31     {32      videoTexture.video.play();33     }34     else35     {36      videoTexture.video.pause();37     }38    }39   }40  }41 42 }

在fps模式下通过鼠标移动控制相机视角

 1 var lastPointerX,lastPointerY; 2 var flag_view="free"//free表示默认的自由移动状态,locked表示锁定鼠标的fps模式状态 3 var flag_locked; 4 var obj_keystate=[]; 5 function onMouseMove(evt) 6 { 7  8  if(flag_view=="locked") 9  {10   evt.preventDefault();11   //绕y轴的旋转角度是根据x坐标计算的12   var rad_y=((scene.pointerX-lastPointerX)/window.innerWidth)*(Math.PI/1);//将鼠标位置的变化转化为相机视角的变化13   var rad_x=((scene.pointerY-lastPointerY)/window.innerHeight)*(Math.PI/1);14   camera0.rotation.y+=rad_y;15   camera0.rotation.x+=rad_x;16  }17  lastPointerX=scene.pointerX;18  lastPointerY=scene.pointerY;19 }20 function onMouseDown(evt)21 {22  if(flag_view=="locked") {23   evt.preventDefault();24  }25 }26 function onMouseUp(evt)27 {28  if(flag_view=="locked") {29   evt.preventDefault();30  }31 }

记录键盘按键状态

 1 function onKeyDown(event) 2 { 3  if(flag_view=="locked") { 4   event.preventDefault(); 5   var key = event.key; 6   obj_keystate[key] = 1;//1表示按下 7  } 8 } 9 function onKeyUp(event)10 {11  var key = event.key;12  if(key=="v"||key=="Escape")//按v键开闭fps模式13  {14   event.preventDefault();15   if(flag_view=="locked")16   {17    flag_view="free";18    document.exitPointerLock();19   }20   else if(flag_view=="free")21   {22    flag_view="locked";23    canvas.requestPointerLock();24   }25  }26  if(flag_view=="locked") {27   event.preventDefault();28 29   obj_keystate[key] = 0;30  }31 }

接下来在渲染循环中根据控制输入确定相机的位移:

var flag_speed=1;    //var m_view=camera0.getViewMatrix();    //var m_view=camera0.getProjectionMatrix();    var m_view=node_temp.getWorldMatrix();    //只检测其运行方向?-》相对论问题!《-先假设直接外围环境不移动    if(obj_keystate["Shift"]==1)//Shift+w的event.key不是Shift和w,而是W!!!!    {     flag_speed=5;//加速移动    }    var delta=engine.getDeltaTime();//两渲染帧之间的时间间隔(毫秒)    //console.log(delta);    flag_speed=flag_speed*engine.getDeltaTime()/10;    var v_temp=new BABYLON.Vector3(0,0,0);    if(obj_keystate["w"]==1)    {     v_temp.z+=0.1*flag_speed;    }    if(obj_keystate["s"]==1)    {     v_temp.z-=0.1*flag_speed;    }    if(obj_keystate["d"]==1)    {     v_temp.x+=0.05*flag_speed;    }    if(obj_keystate["a"]==1)    {     v_temp.x-=0.05*flag_speed;    }    if(obj_keystate[" "]==1)    {     v_temp.y+=0.05*flag_speed;    }    if(obj_keystate["c"]==1)    {     v_temp.y-=0.05*flag_speed;    }    //camera0.position=camera0.position.add(BABYLON.Vector3.TransformCoordinates(v_temp,camera0.getWorldMatrix()).subtract(camera0.position));    //engine.getDeltaTime()              var pos_temp=camera0.position.add(BABYLON.Vector3.TransformCoordinates(v_temp,m_view));

根据按键状态和两帧之间的时间计算出相机在这一帧内的位移向量,需要注意的是这个位移向量以相机的局部坐标系为参考,为了在世界坐标系中使用它,建立了一个node_temp节点专门用来保存相机的姿态矩阵,对位移向量施加这个矩阵变化将它转化为世界坐标系中的位移矩阵。

接下来使用射线进行简单的碰撞检测:

 1 var direction=pos_temp.subtract(pos_last);//pos_last是上一帧的相机位置,取新位置向量减旧位置向量的结果为物体的运动方向 2     //var direction=BABYLON.Vector3.TransformCoordinates(v_temp,m_view);//一次性计算的好处是只需绘制一条射线,缺点是容易射空 3     var ray = new BABYLON.Ray(camera0.position, direction, 1);//从camera0.position位置向direction方向,绘制长度为1的'射线' 4     var arr=scene.multiPickWithRay(ray); 5     arr.sort(sort_compare)//按距离从近到远排序 6     var len=arr.length; 7  8     var flag_hit=false; 9     for(var k=0;k<len;k++)//对于这条射线击中的每个三角形10      {11       var hit=arr[k];12       var mesh=hit.pickedMesh;13       var distance=hit.distance;14       if(mesh||mesh.name)//暂不限制mesh种类15       {16        console.log(mesh.name);17        flag_hit=true;18        break;19       }20      }21     if(!flag_hit)//如果没有被阻拦,则替换位置22     {23      camera0.position=pos_temp;24     }25     else26     {27      camera0.position=pos_last;//回溯的太远了28     }

以上渲染循环中的运动控制代码主要在fps模式下生效,尝试通过在检测到碰撞时调用camera0.position=pos_last;来阻止自由相机穿墙,但效果并不好。

三、总结:

编程结果基本达到设计目标,但在代码冗余、功能细节调试方面尚有不足,接下来可以考虑向程序中添加模型资源作为'雕塑'展示、添加更多类型的零件、添加重力效果、添加WebSocket交互等。

 









原文转载:http://www.shaoqun.com/a/494718.html

promoted:https://www.ikjzd.com/w/971

epa认证:https://www.ikjzd.com/w/1769

一淘比价网:https://www.ikjzd.com/w/1698


设计目标:借鉴前辈编程者的经验将简单的配置文本转化为3D场景,并根据配置文件在场景中加入图片和可播放的视频,最终形成可浏览的3D陈列室。一、使用效果1、txt配置文件:(博客园的富文本编辑器会改变txt文本的排版,所以用图片方式呈现文本)第一行表示陈列室的每一层前后最多有5个房间,左右最多有8个房间,接下来是第一层的地图:"0"表示普通房间,"+、-、|"表示连接房间的通道,"#"表示地面有洞的房
汇通达:汇通达
heap:heap
东杰智能:东杰智能
新手卖家都可以操作的一种模式,无货源你真的了解吗?:新手卖家都可以操作的一种模式,无货源你真的了解吗?
泰国旅游签证怎么办理:泰国旅游签证怎么办理

No comments:

Post a Comment