使用Chrome控制台进行3D模型编辑的尝试,在WebGL场

作者: 前端知识  发布:2019-11-28

前言:3D模型编辑的核心是对顶点位置和纹理颜色的编辑,这个研究的目的在于寻找一种通过编程方式直接对模型进行编辑的方法,这种编辑方法和时下流行的通过鼠标点选、拖拽进行编辑的方法之间的关系,和前端编程中“程序员编写静态网页”与“美工进行网页切图”之间的关系很相似。

在WebGL场景中使用2DA*寻路,webgl2da

     这篇文章将讨论如何在一个自定义的地面网格上进行简单的2D寻路,以及确定路径后如何使用基于物理引擎的运动方式使物体沿路径到达目标地点。读者需要预先对WebGL和Babylonjs知识有一些了解,可以参考我录制的WebGL入门视频教程和翻译的官方入门文档,当然也可以用自己喜欢的其他方式来学习。

  文章主要分成如下几部分:

  1、自定义地面网格与寻路矩阵

  2、生成Babylon格式3D模型

  3、使用pathfinding库进行2D寻路

  4、基于cannon.js物理引擎使物体沿路径移动

  场景可以通过

  场景如下图:

图片 1

  使用WASD控制自由相机位置,移动鼠标控制视角,右键点击地面会在地面上放置一个“目标方块”,然后标有“农民”标志的小球会向目标方块移动。

  场景中使用了2DA*寻路算法,如下图所示:

图片 2

  当目标方块位于障碍的另一边时,农民会尽量寻找最短的路径绕开障碍物前往目标方块。

1、自定义地面网格与寻路矩阵:

a、在Babylon.js渲染引擎中自定义地面网格

 1     var vdata_ground=new BABYLON.VertexData.CreateGround({width:198,height:198,subdivisionsX:99,subdivisionsY:99});//分成了99段,一条边上100个顶点
 2     var arr_vposition=vdata_ground.positions;
 3     var map=[];
 4     var len=arr_vposition.length/3;//和挨个遍历比起来,似乎有目的的去找更好
 5     for(var i=20;i<81;i  )//对于这个范围内的行
 6     {
 7         for(var j=20;j<23;j  )//对于这个范围内的列
 8         {
 9             arr_vposition[(j 100*i)*3 1]=1;
10         }
11     }
12     for(var i=20;i<81;i  )//对于这个范围内的行
13     {
14         for(var j=40;j<43;j  )//对于这个范围内的列
15         {
16             arr_vposition[(j 100*i)*3 1]=2;
17         }
18     }
19     for(var i=20;i<81;i  )//对于这个范围内的行
20     {
21         for(var j=60;j<63;j  )//对于这个范围内的列
22         {
23             arr_vposition[(j 100*i)*3 1]=4;
24         }
25     }
26     BABYLON.VertexData._ComputeSides(0, arr_vposition, vdata_ground.indices, vdata_ground.normals
27         , vdata_ground.uvs);
28     var mesh_ground=new BABYLON.Mesh("mesh_ground",scene);
29     mesh_ground.renderingGroupId=2;
30     vdata_ground.applyToMesh(mesh_ground, true);

   第一行建立了一个Ground类型的Babylonjs“顶点数据”对象,这个对象包含了建立地面网格所需的顶点位置、法线、纹理坐标、顶点索引数据(建议读者亲自用调试模式看一下这个对象的结构),构造函数中的两个198表示地面的长宽是198,两个99表示每一条边被分为99段(由100个顶点组成,每两个顶点之间的距离为2),至于为什么设置为99段后文会有说明,这时的地面网格如果渲染出来将是一个平面。

  要让地面变得凹凸不平有两种思路:在高度变化的点的比例较大时,可以尝试对每个顶点进行遍历,然后按照某种规则改变顶点的高度;在比例较小时建议直接在缓存数组中找到这些顶点进行改变,显然后者速度更快。

  第26行根据顶点数据对每个面的正反进行计算,在对网格的顶点信息进行修改后一般都要执行这一条语句,而另一条经常在它之前执行的语句是:“

BABYLON.VertexData.ComputeNormals(positions, indices, normals);

”,它的作用是在顶点数据变化后重新计算法线方向。这里不执行这条语句的原因是Babylonjs中的地面网格是一种“简化”的网格

  如图所示:

图片 3

  同样表示两个方块,简化的方式使用六个顶点,顶点之间的片元数据由顶点数据插值而成,因为左边的方块和右边的方块共用了两个顶点,所以这两个方块的法线方向和纹理坐标必定是连续的。而用非简化的方式表示这两个方块,则需要使用八个顶点,缺点是增加了对性能的消耗,优点是法线方向和纹理坐标不必连续,可以进行截然不同的变化。

  因为采用了简化的方式,地面网格的同一个顶点处于多个不同的平面中,使用ComputeNormals计算地面网格的顶点的法线方向也就失去了意义,事实上Babylonjs把地面网格顶点的法线方向都默认为竖直向上。

  后面的代码建立了一个空的网格,将网格的渲染组设为2,将顶点数据交给这个空网格对象。

  生成的网格如下图所示:

图片 4

 

b、建立2D寻路矩阵

  在寻路场景中使用的pathfinding库需要用一个矩阵(二维数组)来定义障碍物的位置,其中零元素表示这个地块可以通行,不为零的元素表示无法通行,下面是建立这个数组的方法:

 1     mesh_ground.mydata={};
 2     mesh_ground.mydata.walkabilityMatrix=MakewalkabilityMatrix(arr_vposition,99,99,2);
 3     mesh_ground.mydata.len_x=99;
 4     mesh_ground.mydata.len_y=99;
 5     mesh_ground.mydata.len_s=2;
 6     
 7     。
 8     。
 9     。
10     
11 //对每个正方形区块的倾斜程度进行计算,得出是否可以通行
12 //顶点数据,寻路空间宽度,寻路空间高度,每个方格区域的边长
13 function MakewalkabilityMatrix(arr,len_x,len_y,len_s)
14 {
15     var arr_Matrix=numeric.rep([len_y,len_x],0);
16     var len_s2=len_s*0.707;//求得平均点到其中一个边线点的水平距离0.7071067811865476
17     //var len_s2a=len_s2;
18     //var len_s2b=len_s2;
19     //var len_s2c=len_s2;
20     for(var i=0;i<len_y;i  )//对于每一行寻路单元格
21     {
22         for(var j=0;j<len_x;j  )//对于这一行里的每一个单元格
23         {
24             var int1=j i*(len_x 1);
25             var int2=j i*(len_x 1) 1;
26             var int3=j (i 1)*(len_x 1);
27             var int4=j (i 1)*(len_x 1) 1;
28             var y1=arr[int1*3 1];
29             var y2=arr[int2*3 1];
30             var y3=arr[int3*3 1];
31             var y4=arr[int4*3 1];
32             var ya=(y1 y2 y3 y4)/4;
33             var yb=Math.max(Math.abs(y1-ya),Math.abs(y2-ya),Math.abs(y3-ya),Math.abs(y4-ya));
34             arr_Matrix[i][j]=parseInt((yb)/0.707);//高度超过了水平距离几倍就认为是几倍的障碍物
35         }
36     }
37     return arr_Matrix;
38 }

  这一段代码的思路是:将地面网格垂直方向的正投影作为寻路单元格,取每一个寻路单元格的四个顶点,算出这四个顶点的平均高度与每个顶点高度的差的最大值与寻路单元格中心点到顶点的水平距离的比,将这个比值作为“障碍程度”(简单来说就是顶点高度变化的越剧烈,障碍就越难跨越)。然后为网格添加一个mydata属性(JavaScript语言的优势),把和寻路矩阵有关的信息放到这个属性里。

  这里用到了numeric数学库,可以在

  2、生成babylon格式的3D模型:

  网格生成完毕后需要拿到其他程序中使用,这里我选择把它保存为babylon格式的3D模型,babylon是一种json字符串模型文件,其优点是结构简单功能全面。

  Babylon.js的官方网站上有完整的格式说明和例子:

  以下是生成对应json的代码:

  1 /**
  2  * Created by Administrator on 2017/7/14.
  3  */
  4 function Export_mesh(arr_mesh,PngName)//用Babylon格式导出模型
  5 {
  6     //场景对象
  7     var obj_scene=
  8     {
  9         'autoClear': true,
 10         'clearColor': [0,0,0],
 11         'ambientColor': [0,0,0],
 12         'gravity': [0,-9.81,0],
 13         'cameras': [{
 14             'name': 'Camera',
 15             'id': 'Camera',
 16             'position': [7.4811,5.3437,-6.5076],
 17             'target': [-0.3174,0.8953,0.3125],
 18             'fov': 0.8576,
 19             'minZ': 0.1,
 20             'maxZ': 100,
 21             'speed': 1,
 22             'inertia': 0.9,
 23             'checkCollisions': false,
 24             'applyGravity': false,
 25             'ellipsoid': [0.2,0.9,0.2]
 26         }],
 27         'activeCamera': 'Camera',
 28         'lights': [{
 29             'name': 'Sun',
 30             'id': 'Sun',
 31             'type': 1,
 32             'position': [0.926,7.3608,14.1829],
 33             'direction': [-0.347,-0.4916,-0.7987],
 34             'intensity': 1,
 35             'diffuse': [1,1,1],
 36             'specular': [1,1,1]
 37         }],
 38         'materials':[{
 39             'name': 'mball',
 40             'id': 'mball',
 41             'ambient': [1,1,1],
 42             'diffuse': [1,1,1],
 43             'specular': [1,1,1],
 44             'specularPower': 50,
 45             'emissive': [0,0,0],
 46             'alpha': 1,
 47             'backFaceCulling': true,
 48             'diffuseTexture': {
 49                 'name': PngName?PngName:'snow2.jpg',
 50                 'level': 1,
 51                 'hasAlpha': 1,
 52                 'coordinatesMode': 0,
 53                 'uOffset': 0,
 54                 'vOffset': 0,
 55                 'uScale': 1,
 56                 'vScale': 1,
 57                 'uAng': 0,
 58                 'vAng': 0,
 59                 'wAng': 0,
 60                 'wrapU': true,
 61                 'wrapV': true,
 62                 'coordinatesIndex': 0
 63             }
 64         }],
 65         'geometries': {},
 66         'meshes': [],
 67         'multiMaterials': [],
 68         'shadowGenerators': [],
 69         'skeletons': [],
 70         'sounds': [],
 71         'mydata':{'walkabilityMatrix':[]}
 72     };
 73     //所有模型组件的父物体
 74     var obj_allbase=
 75     {
 76         'name': 'allbase',
 77         'id': 'allbase',
 78         'materialId': 'mball',
 79         'position': [0,0,0],
 80         'rotation': [0,0,0],
 81         'scaling': [1,1,1],
 82         'isVisible': true,
 83         'isEnabled': true,
 84         'checkCollisions': false,
 85         'billboardMode': 0,
 86         'receiveShadows': true,
 87         'positions': [],
 88         'normals': [],
 89         'uvs': [],
 90         'indices': [],
 91         'subMeshes': [{
 92             'materialIndex': 0,
 93             'verticesStart': 0,
 94             'verticesCount': 0,
 95             'indexStart': 0,
 96             'indexCount': 0
 97         }]
 98     };
 99     obj_scene.meshes.push(obj_allbase);
100     var len=arr_mesh.length;
101     var all_x=0;
102     var all_y=0;
103     var all_z=0;
104     for(var i=0;i<len;i  )
105     {
106         var obj_child={};
107         if(arr_mesh[i].geometry._vertexBuffers!=null)
108         {
109             var child=arr_mesh[i];
110             if(!child.mydata)
111             {
112                 child.mydata={}
113             }
114             var vb=child.geometry._vertexBuffers;
115             all_x =child.position.x;
116             all_y =child.position.y;
117             all_z =child.position.z;
118             obj_child=
119             {
120                 'name': child.name,
121                 'id': child.id,
122                 'parentID': 'allbase',
123                 'materialId': 'mball',
124                 'position': [child.position.x,child.position.y,child.position.z],
125                 'rotation': [child.rotation.x,child.rotation.y,child.rotation.z],
126                 'scaling': [child.scaling.x,child.scaling.y,child.scaling.z],
127                 'isVisible': true,
128                 'isEnabled': true,
129                 'checkCollisions': false,
130                 'billboardMode': 0,
131                 'receiveShadows': true,
132                 'positions': vb.position._buffer._data,
133                 'normals': vb.normal._buffer._data,
134                 'uvs': vb.uv._buffer._data,
135                 'indices': child.geometry._indices,
136                 'subMeshes': [{
137                     'materialIndex': 0,
138                     'verticesStart': 0,
139                     'verticesCount': vb.position._buffer._data.length,
140                     'indexStart': 0,
141                     'indexCount': child.geometry._indices.length
142                 }],
143                 'mydata':child.mydata
144             };
145             obj_scene.meshes.push(obj_child);
146         }
147     }
148     //不能让模型的主体过于偏离模型的中心
149     all_x=all_x/len;
150     all_y=all_y/len;
151     all_z=all_z/len;
152     for(var i=1;i<len 1;i  )
153     {
154         obj_scene.meshes[i].position[0]-=all_x;
155         obj_scene.meshes[i].position[1]-=all_y;
156         obj_scene.meshes[i].position[2]-=all_z;
157     }
158     var str_data=JSON.stringify(obj_scene);
159     DownloadText(MakeDateStr() "testscene",str_data,".babylon");
160 }

  可以看出,一个babylon文件可以包含多个网格对象,除了网格对象之外这个模型文件还可以存储场景、光照、相机、动画、骨骼等信息,这些功能可以选择性使用。方法的最后使用DownloadText方法将json文本导出,DownloadText是我参考网络资料编写的字符下载方法,如果不使用DownloadText,直接在Chrome浏览器的调试模式下的命令行里输入“console.log(str_data)”也能得到json字符串。

  DownloadText内容如下:

图片 5

  1 /**
  2  * Created by Administrator on 2015/3/2.
  3  */
  4 /**
  5  * 将指定字符写入指定名称的文本文件中,并可以选择本地保存目录,兼容IE11和谷歌浏览器
  6  */
  7 function DownloadText(filename,content,filetype)
  8 {
  9     if(filetype==null)
 10     {
 11         filetype=".txt";
 12     }
 13     if(document.createElement("a").download!=null)//谷歌和火狐
 14     {
 15         var aLink = document.createElement('a');
 16         var datatype="data:text/plain;charset=UTF-8,";
 17         if(filetype==".xml")
 18         {
 19             datatype="data:text/xml;charset=UTF-8,";
 20         }
 21         if(filetype==".babylon")
 22         {//浏览器还没有支持babylon的mime类型!!
 23             datatype="data:text/plain;charset=UTF-8,";
 24         }
 25         if(filetype==".png"||filetype==".jpeg")
 26         {
 27             datatype="";
 28         }
 29         if(content.length<1000000)
 30         {
 31             aLink.href = datatype content;//dataurl格式的字符串"
 32         }
 33         else
 34         {//对于过大的文件普通dataURL不支持,所以使用“二进制流大对象”
 35             aLink.href=URL.createObjectURL(new Blob([content],{type:"text/plain"}));
 36         }
 37         aLink.download = filename;
 38         aLink.innerHTML=filename;
 39         //aLink.setAttribute("onclick","");
 40         aLink.onclick=function()
 41         {
 42             document.getElementById("div_choose").style.display="none";
 43             //delete_div('div_choose');
 44             delete_div('div_mask');
 45         }
 46         //aLink.style.display="none";
 47         //document.body.appendChild(aLink);
 48         /*var evt = document.createEvent("HTMLEvents");//建立一个事件
 49         evt.initEvent("click", false, false);//这是一个单击事件
 50         evt.eventType = 'message';
 51         aLink.dispatchEvent(evt);//触发事件*/
 52         //chrome认为点击超链接下载文件是超链接标签的“默认属性”,谷歌认为默认属性不可以用脚本来触发,所以从M53版本开始dispatchEvent无法触发超链接下载
 53         //window.open(datatype content, "_blank");
 54         //document.write(datatype content);
 55         delete_div('div_choose');
 56         delete_div('div_mask');
 57         var evt=evt||window.event;
 58         cancelPropagation(evt);
 59         var obj=evt.currentTarget?evt.currentTarget:evt.srcElement;
 60 
 61         Open_div("", "div_choose", 240, 180, 400, 80, "", "",1,401);//打开一个带遮罩的弹出框
 62         var div_choose=$("#div_choose")[0];
 63         div_choose.style.border="1px solid";
 64         div_choose.innerHTML="谷歌浏览器专用文件生成完毕,请点击下面的文件名下载文件。<br>"
 65         div_choose.appendChild(aLink);
 66         drag(div_choose);//让弹出框可以被拖拽
 67         aLink.onmousedown=function()
 68         {
 69             var evt=evt||window.event;
 70             cancelPropagation(evt);
 71         }
 72     }
 73     else//IE
 74     {
 75         var Folder=BrowseFolder();
 76         if(Folder=="false")
 77         {
 78             alert("保存失败!");
 79         }
 80         else
 81         {
 82             var fso, tf;
 83             fso = new ActiveXObject("Scripting.FileSystemObject");//创建文件系统对象
 84             tf = fso.CreateTextFile(Folder   filename filetype, true,true);//创建一个文件
 85             tf.write(content);
 86             tf.Close();
 87             alert("保存完毕!");
 88         }
 89     }
 90 }
 91 function BrowseFolder()
 92 {//使用ActiveX控件
 93     try
 94     {
 95         var Message = "请选择保存文件夹";  //选择框提示信息
 96         var Shell = new ActiveXObject( "Shell.Application" );
 97         var Folder = Shell.BrowseForFolder(0,Message,0x0040,0x11);//起始目录为:我的电脑
 98         //var Folder = Shell.BrowseForFolder(0,Message,0); //起始目录为:桌面//选择桌面会报错!!
 99 
100         if(Folder != null)
101         {
102             Folder = Folder.items();  // 返回 FolderItems 对象
103             Folder = Folder.item();  // 返回 Folderitem 对象
104             Folder = Folder.Path;   // 返回路径
105             if(Folder.charAt(Folder.length-1) != "\")
106             {
107                  Folder = Folder   "\";
108             }
109             //document.all.savePath.value=Folder;
110             return Folder;
111         }
112     }
113     catch(e)
114     {
115         return "false";
116         alert(e.message);
117     }
118 }

View Code

  接下来,我们要在另一个程序中使用上面生成的模型文件,使用Babylonjs的资源管理器加载网格:

 1     this.loader =  new BABYLON.AssetsManager(this.scene);//资源管理器
 2 
 3     // 资源数组
 4     this.assets = {};
 5     //为资源管理器分配一个任务
 6     var meshTask = this.loader.addMeshTask("gun", "", "./assets/", "gun.babylon");
 7     meshTask.onSuccess = function(task) {//这个任务完成
 8         _this._initMesh(task);
 9     };    
//第一个参数表示task的name,第二个参数表示加载模型文件中的哪个网格,为空则用数组形式加载全部,第三个参数表示路径,第四个参数是文件名
10     var meshTask2 = this.loader.addMeshTask("mesh_ground", "", "./assets/arena/", "2017810_14_12_59testscene.babylon");
11     meshTask2.onSuccess = function(task) {
12         _this._initMesh(task);
13     };
14 
15     this.loader.onFinish = function (tasks)//所有任务完成
16     {
17     。。。
18     }
19     
20     。
21     。
22     。
23     
24     _initMesh : function(task)
25     {
26         this.assets[task.name] = task.loadedMeshes;
27         for (var i=0; i<task.loadedMeshes.length; i   ){
28             var mesh = task.loadedMeshes[i];
29             mesh.isVisible = false;
30             //预先把所有资源加载下来,但不显示,当需要时再把它显示在需要的位置,或者在需要的位置,建立一个资源的实例(克隆)
31         }
32     }
33     

  这时,会发生一个小问题:Babylonjs并不支持我们夹带在mesh中的mydata属性。解决方法是在babylon.30.all.max.js的21272行附近修改:

1             if (parsedMesh.metadata !== undefined) {
2                 mesh.metadata = parsedMesh.metadata;
3             }
4             if (parsedMesh.mydata !== undefined) {
5                 mesh.mydata = parsedMesh.mydata;
6             }    

  仿照metadata的写法加上对mydata的支持,当然,也可以考虑把mydata夹带到其他被mesh所支持的属性里。

   3、使用pathfinding库进行2D寻路

  pathfindingjs是一个开源2D寻路库,可以在

  pathfinding的基本用法如下:

 1 var finder = new PF.AStarFinder({//“寻路器”
 2     diagonalMovement: 3
 3 });
 4 
 5 。
 6 this.grid=new PF.Grid(this.len_x,this.len_y,this.walkabilityMatrix);//生成寻路网格 7 。


 8 
 9 function FindWaytogo(pickResult)//pickResult是Babylonjs中定义的“鼠标选取结果”对象
10 {
11     var faceId=pickResult.faceId;//点击了网格中的第几个面
12     var pickedMesh=pickResult.pickedMesh;//被点击的网格
13     var px=MyGame.player.mesh.position.x;//被控对象在场景中的水平位置
14     var py=MyGame.player.mesh.position.z;
15 
16     var len_x=MyGame.arena.len_x;//寻路网格的格数和每格的长度
17     var len_y=MyGame.arena.len_y;
18     var len_s=MyGame.arena.len_s;
19     if(px>-len_x*len_s/2&&px<len_x*len_s/2&&py>-len_y*len_s/2&&py<len_y*len_s/2&&MyGame.arena.grid)//如果使用了pathfinder的障碍矩阵
20     {
21         var arr_matrix=MyGame.arena.walkabilityMatrix;//寻路矩阵
22         var count=parseInt(faceId/2);//第几个方格
23         //接下来要把网格的面转换为寻路方格的坐标,后面还要把寻路方格的坐标转换为scene中的位置
24         //面数转换为方格坐标
25         var count_y=parseInt(count/len_x);
26         var count_x=count%len_x;
27         //场景坐标转化为方格坐标
28         var count_x0=parseInt(px/len_s len_x/2);
29         var count_y0=parseInt(-py/len_s len_y/2);
30 
31         //寻路,返回一个由方格坐标组成的数组
32         var path = finder.findPath(count_x0,count_y0 , count_x,count_y, MyGame.arena.grid.clone());//这些是寻路网格坐标
33         var len=path.length;
34         for(var i=0;i<len;i  )
35         {//把方格坐标转化为场景坐标
36             var obj=path[i];
37             obj[0]=(obj[0]-len_x/2)*len_s;
38             obj[1]=(-obj[1] len_y/2)*len_s;
39         }
40 
41         path.push([pickResult.pickedPoint.x,pickResult.pickedPoint.z]);
42         MyGame.player.path_goto=path;//在使用时在生成高度
43         MyGame.player.positiontogo=[pickResult.pickedPoint.x,pickResult.pickedPoint.z];
44         path.shift();//把第一个出发节点去掉
45         console.log("生成路径,起点:[" px "," py "],终点:[" pickResult.pickedPoint.x "," pickResult.pickedPoint.z "]");
46     }
47 }

  这样,我们就把场景中的位置对应成了寻路网格中的位置,然后使用pathfinding生成了2D路径。需要注意的是pathfinding中的grid对象只能使用一次,再次寻路时需要重新生成grid或者使用grid的克隆对象。

 

  4、基于cannon.js物理引擎沿路径移动 

  接下来需要让被控物体沿着指定的路径运动,为了能让物体在凹凸不平的地形中运动时保持紧贴地面,我在这里使用了cannonjs物理引擎(关于物理引擎的用法可以参考上一篇文章)。经过试验,这个版本的cannonjs的单个物理仿真器最多支持对10000个顶点的物理仿真,所以前文没法把地面网格分成更多段。

  我们在这个场景中监听“右键点击地面”的事件,代码如下:

 1 canvas.addEventListener("click", function(evt) {
 2             var width = engine.getRenderWidth();
 3             var height = engine.getRenderHeight();
 4             var pickInfo = scene.pick(width/2, height/2, null, false, _this.camera);//点击信息
 5             if(evt.button==2)//右键单击
 6             {
 7                 cancelEvent(evt);//阻止默认响应
 8                 if(pickInfo.hit&&pickInfo.pickedMesh.name=="mesh_ground")//点击到了地面上
 9                 {
10                     MyGame.player.mesh.physicsImpostor.setMass(70);//给被控物体赋予质量,这样它才可以下落
11                     FindWaytogo(pickInfo);//在玩家到点击目的地之间找到一条路径
12                     var mesh_togo=BABYLON.Mesh.CreateBox("box", 1, scene);//目标方块
13                     mesh_togo.position = pickInfo.pickedPoint.clone();//pickResult.pickedPoint
14                     mesh_togo.renderingGroupId=2;
15                     MyGame.player.mesh_togo=mesh_togo;
16                 }
17             }
18 
19 
20 
21         }, false);

  然后在每次渲染之前执行以下运动方法:

 1         scene.registerBeforeRender(function() {
 2             if(MyGame.flag_startr==1)//如果开始渲染了
 3             {
 4                 if(MyGame.flag_view=="first"||MyGame.flag_view=="third")
 5                 {
 6                     physics20170725(MyGame.player);
 7                 }
 8                 if(MyGame.flag_view=="free")
 9                 {
10                     pathgoto20170808(MyGame.player);
11                 }
12             }
13         });

 

 1 function pathgoto20170808(obj)//obj是player
 2 {
 3     if(true)
 4     //if(obj.standonTheGround==1)//站在地面上时考虑将质量设为0?
 5     {
 6         if(obj.path_goto!="sleep"&&obj.path_goto!="lose")
 7         {
 8             var len_x=MyGame.arena.len_x;
 9             var len_y=MyGame.arena.len_y;
10             var len_s=MyGame.arena.len_s;
11             var vl_now=obj.mesh.physicsImpostor.getLinearVelocity();
12 
13             if(obj.path_goto.length>0)
14             {
15                 var px=obj.mesh.position.x;//全是场景坐标!!
16                 var py=obj.mesh.position.z;
17                 var count_x0=px;
18                 var count_y0=py;
19                 var count_x=obj.path_goto[0][0];
20                 var count_y=obj.path_goto[0][1];
21                 var len=obj.path_goto.length;
22                 var count_x2=obj.path_goto[len-1][0];
23                 var count_y2=obj.path_goto[len-1][1];
24                 var y_obj=obj.mesh.position.y;
25                 if((Math.pow(count_x0-count_x2,2) Math.pow(count_y0-count_y2,2))<0.25*len_s*len_s)
26                 {//在移动过程中因未知原因跳到距终点0.5以内距离的地方,直接寻找最终点
27                     console.log("在最终格内");
28                     if((Math.pow(count_x0-count_x2,2) Math.pow(count_y0-count_y2,2))<0.01*len_s*len_s)//到达0.1距离以内的地方,认为到达最终目标,直接定位
29                     {
30 
31                         obj.mesh.position.x=count_x;
32                         obj.mesh.position.z=count_y;
33                         obj.path_goto="sleep";
34                         console.log("到达最终目标:[" obj.mesh.position.x "," obj.mesh.position.y "," obj.mesh.position.z "]");
35                         obj.mesh.physicsImpostor.setMass(0);//质量设为零将不会下落
36                         obj.mesh_togo.dispose();
37                         obj.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,0,0));//停下
38                         obj.mesh.physicsImpostor.setLinearVelocity(new BABYLON.Vector3(0,0,0));
39                         
40                     }
41                     else{
42                         var v_temp=new BABYLON.Vector3(count_x2,0,count_y2).subtract(new BABYLON.Vector3(obj.mesh.position.x,0,obj.mesh.position.z)).normalize().scaleInPlace(obj.vm.forward);
43                         v_temp.y=vl_now.y<=0?vl_now.y:0;//这个单位应该脚踏实地的平稳运动
44                         obj.mesh.physicsImpostor.setLinearVelocity(v_temp);
45                         //obj.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,0,0));//停下
46                         if(obj.path_goto.length>1)
47                         {
48                             obj.path_goto=[obj.path_goto[len-1]];//只剩一个最终目标
49                         }
50                     }
51                 }
52                 else if((Math.pow(count_x0-count_x,2) Math.pow(count_y0-count_y,2))>4*len_s*len_s)
53                 {//在移动过程中因未知原因跳到距下个目标格2以外距离的地方,需要重新寻路,这种计算可能耗时较大,不能每帧执行!!
54                     obj.path_goto="lose";
55                     //obj.mesh.physicsImpostor.setMass(0);//不掉落
56                     obj.mesh.physicsImpostor.setLinearVelocity(new BABYLON.Vector3(0,0,0));
57                     obj.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,0,0));//停下
58                     return false;
59                 }
60                 else if(obj.path_goto.length>1&&(Math.pow(count_x0-count_x,2) Math.pow(count_y0-count_y,2))<0.25*len_s*len_s)
61                 {//距离下一寻路格足够近,切换下一寻路格
62 
63                     obj.path_goto.shift();
64                     count_x=obj.path_goto[0][0];
65                     count_y=obj.path_goto[0][1];
66                     console.log("切换下一个寻路单元格:[" count_x "," count_y "]");
67                     var v_temp=new BABYLON.Vector3(count_x,0,count_y).subtract(new BABYLON.Vector3(obj.mesh.position.x,0,obj.mesh.position.z)).normalize().scaleInPlace(obj.vm.forward);
68                     v_temp.y=vl_now.y<=0?vl_now.y:0;
69                     obj.mesh.physicsImpostor.setLinearVelocity(v_temp);
70                     //obj.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,0,0));//停下
71                 }
72                 else//正常向目标寻路格移动
73                 {
74                     console.log("普通寻路");
75                     var v_temp=new BABYLON.Vector3(count_x,0,count_y).subtract(new BABYLON.Vector3(obj.mesh.position.x,0,obj.mesh.position.z)).normalize().scaleInPlace(obj.vm.forward);
76                     v_temp.y=vl_now.y<=0?vl_now.y:0;
77                     obj.mesh.physicsImpostor.setLinearVelocity(v_temp);
78                     //obj.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,0,0));//停下
79                 }
80 
81             }
82         }
83         else
84         {
85             //obj.mesh.physicsImpostor.setMass(0);//不掉落
86         }
87     }
88 
89 
90 
91 }

  这里分几种可能发生的运动情况(最常见的几种)分别设置被控物体的线速度,使得物体平稳的沿着路径运动,当物体到达目标时将进入sleep状态,当物体偏离路径时将进入lose状态,程序每秒钟检查一下物体是否lose,如果lose则重新寻路(没有测试过):

 1 _this.currentframet=new Date().getTime();
 2                 _this.DeltaTime=_this.currentframet-_this.lastframet;//取得两帧之间的时间
 3                 _this.lastframet=_this.currentframet;
 4                 _this.nohurry =_this.DeltaTime;
 5                 if(MyGame&&_this.nohurry>1000)//每一秒进行一次导航修正
 6                 {
 7                     _this.nohurry=0;
 8                     if(_this.player.path_goto=="lose")//发现迷失了路途
 9                     {
10                         console.log("发现迷路,重新规划路径");
11                         var len_x=MyGame.arena.len_x;
12                         var len_y=MyGame.arena.len_y;
13                         var len_s=MyGame.arena.len_s;
14                         //场景坐标转化为方格坐标
15                         var count_x0=parseInt(_this.player.mesh.position.x/len_s len_x/2);
16                         var count_y0=parseInt(-_this.player.mesh.position.z/len_s len_y/2);
17                         var count_x=parseInt(_this.player.positiontogo[0]/len_s len_x/2);
18                         var count_y=parseInt(-_this.player.positiontogo[1]/len_s len_y/2);
19                         var path = finder.findPath(count_x0,count_y0 , count_x,count_y, MyGame.arena.grid.clone());//这些是寻路网格坐标
20                         var len=path.length;
21                         for(var i=0;i<len;i  )
22                         {//把方格坐标转化为场景坐标
23                             var obj=path[i];
24                             obj[0]=(obj[0]-len_x/2)*len_s;
25                             obj[1]=(-obj[1] len_y/2)*len_s;
26                         }
27                         path.push(MyGame.player.positiontogo);
28                         path.shift();//把第一个出发节点去掉
29                         MyGame.player.path_goto=path;//在使用时在生成高度
30                         console.log("生成路径,起点:[" _this.player.mesh.position.x "," _this.player.mesh.position.z "]"  
31                             ",终点:[" _this.player.positiontogo[0] "," _this.player.positiontogo[1] "]");
32                     }
33                 }

 

  这样,我们就成功的完成了在WebGL场景中寻路的目标,接下来可以尝试修改pathfinding使之能根据不同地形进行加权寻路,以及控制多个单位进行寻路行为。

  

  

这篇文章将讨论如何在一个自定义的地面网格上进行简单的2D寻路,以及确定路径后如何使用基于物理引...

一、工具用法:

1、访问 

图片 6

在场景世界坐标系的(0,-10,0),(0,0,0),(0,10,0)处各有一个绿色小球作为参考点,使用上下左右和鼠标拖动可以进行场景漫游。

2、按F12键打开Chrome控制台,在控制台中输入:MakeRibbon(MakeRing(5,12),-10,2,11,"mesh_ribbon")回车:

图片 7

在场景中绘制了一个半径为5,曲面细分度为12,左端位于-10,每两个圆环间距2,共由11个圆环组成的圆柱面。

拉近查看:

图片 8

3、输入ShowNormals(mesh_origin)将用红色线段显示每个顶点的法线方向

图片 9

输入DeleteMeshes([lines_normal])可以删除所有的法线,输入DeleteMeshes([mesh_origin])则删除圆柱面网格。

4、鼠标移入网格上的三角形,会显示三角形的顶点信息:

图片 10

其中“1:2-5”表示这是三角形的第一个顶点,这个顶点位于索引是2的圆环上(第三个圆环),这个顶点在圆环中的索引是5(也就是第六个顶点)。

5、输入PickPoints([[2,5],[3,5],[2,6]],mesh_origin)可以选定这些顶点

图片 11

被选中顶点所影响的所有边框线标示为黄色,这个“选中”只是改变外观而已。

6、输入TransVertex(mesh_origin,arr_ij,BABYLON.Matrix.Translation(0,0,-10))将所选的顶点向z轴负方向移动10,被移动的顶点和前面选中的顶点其实没有关系,其中arr_ij也可以直接用索引数组[[2,5],[3,5],[2,6]]代替。

图片 12

另一类变形可以通过输入:TransVertex(mesh_origin,arr_ij,BABYLON.Matrix.RotationX(Math.PI/2))实现,这可以把被选中的顶点绕X中旋转90度。

输入DeleteMeshes([lines_inpicked])取消被选中的效果,输入ChangeMaterial(mesh_origin,mat_blue)将边框换成蓝色纹理:

图片 13

可以看到变形后的效果,接下来还可以继续选择顶点并变形

7、输入ExportMesh("1",mat_blue),以txt格式导出babylon模型文件,文件名为“1.txt”

图片 14

 8、将导出的txt改名为9.babylon后放入网站目录中,访问

二、编程思路:

1、首先要建立一个可以进行各种测试的基础场景,使用的代码如下:

  1 <!DOCTYPE html>
  2 <html lang="en">
  3 <head>
  4     <meta charset="UTF-8">
  5     <title>建立一个条带网格生成器,能够输入参数生成起始条带,然后通过命令行选取并修改pathArray,最后导出生成的条带</title>
  6     <link href="../CSS/newland.css" rel="stylesheet">
  7     <link href="../CSS/stat.css" rel="stylesheet">
  8     <script src="../JS/MYLIB/Events.js"></script>
  9     <script src="../JS/MYLIB/FileText.js"></script>
 10     <script src="../JS/MYLIB/View.js"></script>
 11     <script src="../JS/LIB/babylon.32.all.maxs.js"></script><!--V3.2的稳定版本-->
 12     <script src="../JS/MYLIB/newland.js"></script>
 13     <script src="../JS/LIB/stat.js"></script>
 14 </head>
 15 <body>
 16 <div id="div_allbase">
 17     <canvas id="renderCanvas"></canvas>
 18     <div id="fps" style="z-index: 301;"></div>
 19 </div>
 20 </body>
 21 <script>
 22     var VERSION=1.0,AUTHOR="lz_newland@163.com";
 23     var machine,canvas,engine,scene,gl,MyGame={};
 24     canvas = document.getElementById("renderCanvas");
 25     engine = new BABYLON.Engine(canvas, true);
 26     gl=engine._gl;//可以结合使用原生OpenGL和Babylon.js;
 27     scene = new BABYLON.Scene(engine);
 28     var divFps = document.getElementById("fps");
 29 
 30     window.onload=beforewebGL;
 31     function beforewebGL()
 32     {
 33         if(engine._webGLVersion==2.0)//输出ES版本
 34         {
 35             console.log("ES3.0");
 36         }
 37         else{
 38             console.log("ES2.0");
 39         }
 40         //MyGame=new Game(0,"first_pick","","http://127.0.0.1:8082/");
 41         /*0-startWebGL
 42          * */
 43         webGLStart();
 44     }
 45     //从下面开始分成简单测试和对象框架两种架构
 46     //简单测试
 47     //全局对象
 48     var light0//全局光源
 49             ,camera0//主相机
 50             ;
 51     //四种常用材质
 52     var mat_frame = new BABYLON.StandardMaterial("mat_frame", scene);
 53     mat_frame.wireframe = true;
 54     var mat_red = new BABYLON.StandardMaterial("mat_red", scene);
 55     mat_red.diffuseColor = new BABYLON.Color3(1, 0, 0);
 56     mat_red.backFaceCulling=false;
 57     var mat_green = new BABYLON.StandardMaterial("mat_green", scene);
 58     mat_green.diffuseColor = new BABYLON.Color3(0, 1, 0);
 59     mat_green.backFaceCulling=false;
 60     var mat_blue = new BABYLON.StandardMaterial("mat_blue", scene);
 61     mat_blue.diffuseColor = new BABYLON.Color3(0, 0, 1);
 62     mat_blue.backFaceCulling=false;
 63     var mesh_origin;
 64     var advancedTexture=BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("ui1");//全屏GUI
 65     function webGLStart()
 66     {
 67         window.addEventListener("resize", function () {//自动调整视口尺寸
 68             engine.resize();
 69         });
 70         camera0 =new BABYLON.FreeCamera("FreeCamera", new BABYLON.Vector3(0, 0, -80), scene);
 71         camera0.attachControl(canvas, true);
 72         camera0.speed=0.5;//相机移动速度是默认速度的一半
 73         camera0.minZ=0.01;//相机位置距前视锥截面的距离,也就是说到相机距离小于0.01的图元都不会显示,这个值不能过小,否则Babylon.js内置的鼠标选取将失效
 74         camera0.layerMask=2;//相机的遮罩层次,这个相机将只能显示遮罩层次同为2的网格,如果不设置这个属性,似乎可以显示所有遮罩层次的网格
 75         scene.activeCameras.push(camera0);//将相机加入活跃相机列表,默认情况下Babylon.js只使用一个活跃相机,但是也可以强行使用多个
 76         light0 = new BABYLON.HemisphericLight("Hemi0", new BABYLON.Vector3(0, 1, 0), scene);//半球光源
 77      //三个参照物,MeshBuilder是新版Babylon.js中使用的网格构建对象,之前翻译入门教程时还没有这个对象,它的特点是把一大堆参数统一整理到一个option参数中
 78         var mesh_base=new BABYLON.MeshBuilder.CreateSphere("mesh_base",{diameter:1},scene);
 79         mesh_base.material=mat_green;
 80         mesh_base.position.x=0;
 81         mesh_base.layerMask=2;
 82         var mesh_base1=new BABYLON.MeshBuilder.CreateSphere("mesh_base1",{diameter:1},scene);
 83         mesh_base1.position.y=10;
 84         mesh_base1.position.x=0;
 85         mesh_base1.material=mat_green;
 86         mesh_base1.layerMask=2;
 87         var mesh_base2=new BABYLON.MeshBuilder.CreateSphere("mesh_base2",{diameter:1},scene);
 88         mesh_base2.position.y=-10;
 89         mesh_base2.position.x=0;
 90         mesh_base2.material=mat_green;
 91         mesh_base2.layerMask=2;
 92       。
 93       。
 94       。
 95         MyBeforeRender();
 96     }
 97     function MyBeforeRender()
 98     {
 99         scene.registerBeforeRender(function() {
100             if(scene.isReady())
101             {
102                 。
103                 。
104                 。
105                 。
106                 。
107                 。
108             }
109         });
110         engine.runRenderLoop(function () {
111             engine.hideLoadingUI();
112             if (divFps) {
113                 // Fps
114                 divFps.innerHTML = engine.getFps().toFixed()   " fps";
115             }
116             scene.render();
117         });
118 
119     } 
120 </script>
121 </html>   

这个3D场景包括了简单测试所需要的一些基本元素,这里使用的是包含全部组件的未压缩版Babylon.js库,在实际使用中考虑到节省带宽,可以使用Babylon.js官网提供的工具定制精简版或压缩版的Babylon.js库

2、建立一个基础网格

计划通过对一个基础网格进行顶点变换来产生各种各样的简单模型,在Babylon.js中“条带”是一种非常适合顶点变换的网格类型,Babylon.js官方教程中有关于条带构造和变形的文档,可以在这里下载中文翻译

用来建立基础网格的代码如下:

 1     //下面这些函数都通过控制台调用
 2     //在ZoY平面里建立一个圆环路径
 3     //radius:半径,sumpoint:使用几个点
 4     function MakeRing(radius,sumpoint)
 5     {
 6         var arr_point=[];
 7         var radp=Math.PI*2/sumpoint;
 8         for(var i=0.0;i<sumpoint;i  )
 9         {
10             var x=0;
11             var rad=radp*i;
12             //var y=sswr(radius*Math.sin(rad),null,5);//在这里需要降低一些精确度?否则Babylon.js在计算顶点数据时可能和这里不一致?
13             //var z=sswr(radius*Math.cos(rad),null,5);
14             var y=radius*Math.sin(rad);
15             var z=radius*Math.cos(rad);
16             arr_point.push(new BABYLON.Vector3(x,y,z));
17         }
18         arr_point.push(arr_point[0].clone());//首尾相连,不能这样相连,否则变形时会多出一个顶点!!,看来这个多出的顶点无法去掉,只能在选取时额外处理它
19         return arr_point;
20     }
21     var arr_path=[];//核心数据
22     //arr_point:单个路径的点数组,xstartl:第一个扁平路径放在最左侧,spacing:路径的间距,sumpath:一共使用几条路径,
23     function MakeRibbon(arr_point,xstartl,spacing,sumpath,name)
24     {//将一条圆环路径扩展成相互平行的多个圆环路径,然后使用这些路径生成条带
25         arr_path=[];
26         for(var i=0;i<sumpath;i  )//对于每一条路径
27         {
28             var x=xstartl spacing*i;
29             //var arr=arr_point.concat(null);//为什么拷贝失灵了?
30             //var [ ...arr ] = arr_point;//ES6的新扩展运算符?-》也不好使,因为数组里的元素是指针?!!
31             var len=arr_point.length;
32             var arr=[];
33             for(var j=0;j<len;j  )
34             {
35                 var obj=arr_point[j].clone();
36                 obj.x=x;
37                 //
38                 arr.push(obj);
39             }
40             arr_path.push(arr);
41             arr=null;
42         }
43         mesh_origin.dispose();
44         mesh_origin=BABYLON.MeshBuilder.CreateRibbon(name,{pathArray:arr_path,updatable:true,closePath:false,closeArray:false});
45         //mesh_origin=mesh;//用一个全局变量保存最终会被导出的mesh
46         mesh_origin.sideOrientation=BABYLON.Mesh.DOUBLESIDE;//显示网格的前后两面
47         mesh_origin.material=mat_frame;
48         mesh_origin.layerMask=2;
49     }

编程中遇到的几个问题:

a、路径中设置的坐标值在实际显示时可能发生微小的变化,比如5可能变成4.999999999999999999,但是似乎没什么影响。

b、虽然圆环路径分成12段应该由12个顶点组成,但是如果只有12个顶点,那么在调用CreateRibbon方法时,如果把closePath参数设为true则Babylon.js会自动添加一个不受控制的顶点来闭合圆环路径,如果设为false,则路径无法闭合。为此在第18行再添加一个与起始点

重合的顶点使圆环路径闭合。

c、原计划直接使用数组的concat方法复制arr_point数组产生更多的圆环路径,但concat方法似乎只能对一维数组使用(栈?),而arr_point的每个元素都是一个BABYLON.Vector3对象,所以只好用for循环一个点一个点的生成路径

d、MakeRing是一个生成圆环路径的方法,使用者可以根据需要编写各种其他类型的路径生成方法。

3、调整网格的属性:

可以对网格的属性进行一些调整,但是这些调整只在这个编辑器里生效,并不会被导出。

比如对网格材质的调整:

1 function ChangeMaterial(mesh,mat)
2     {
3         mesh.material=mat;
4     }

4、显示网格顶点法线方向

代码如下:

 1 var lines_normal={};
 2     /*ShowNormals(mesh_origin)
 3      DeleteMeshes([lines_normal]);
 4     * */
 5     //显示所有的顶点法线
 6     function ShowNormals(mesh)
 7     {
 8         //DeleteMeshes(arr_line_normal);
 9         if(lines_normal.dispose)
10         {
11             lines_normal.dispose();
12         }
13         //遍历顶点
14         var vb=mesh.geometry._vertexBuffers;
15         var data_pos=vb.position._buffer._data;//顶点数据
16         var data_mormal=vb.normal._buffer._data;//法线数据
17         var len=data_pos.length;
18         var lines=[];
19         for(var i=0;i<len;i =3)
20         {//CreateLineSystem使用一个网格包含很多分立的线段(路径),CreateLines则是一条首尾相连的路径
21             //
22             var vec=new BABYLON.Vector3(data_pos[i],data_pos[i 1],data_pos[i 2]);
23             var vec2=vec.clone().add(new BABYLON.Vector3(data_mormal[i],data_mormal[i 1],data_mormal[i 2]).normalize().scale(1));
24             lines.push([vec,vec2]);
25         }
26         lines_normal=new BABYLON.MeshBuilder.CreateLineSystem("lines_normal",{lines:lines,updatable:false},scene);
27         lines_normal.color=new BABYLON.Color3(1, 0, 0);
28     }

本文由金沙澳门官网发布于前端知识,转载请注明出处:使用Chrome控制台进行3D模型编辑的尝试,在WebGL场

关键词: 金沙澳门官网