前言:3D模型編輯的核心是對頂點位置和紋理顏色的編輯,這個研究的目的在於尋找一種經過編程方式直接對模型進行編輯的方法,這種編輯方法和時下流行的經過鼠標點選、拖拽進行編輯的方法之間的關係,和前端編程中「程序員編寫靜態網頁」與「美工進行網頁切圖」之間的關係很類似。css
1、工具用法:html
一、訪問 https://ljzc002.github.io/SnowMiku/HTML/MakeRibbon.html打開條帶網格生成器頁面前端
在場景世界座標系的(0,-10,0),(0,0,0),(0,10,0)處各有一個綠色小球做爲參考點,使用上下左右和鼠標拖動能夠進行場景漫遊。git
二、按F12鍵打開Chrome控制檯,在控制檯中輸入:MakeRibbon(MakeRing(5,12),-10,2,11,"mesh_ribbon")回車:程序員
在場景中繪製了一個半徑爲5,曲面細分度爲12,左端位於-10,每兩個圓環間距2,共由11個圓環組成的圓柱面。github
拉近查看:web
三、輸入ShowNormals(mesh_origin)將用紅色線段顯示每一個頂點的法線方向算法
輸入DeleteMeshes([lines_normal])能夠刪除全部的法線,輸入DeleteMeshes([mesh_origin])則刪除圓柱面網格。編程
四、鼠標移入網格上的三角形,會顯示三角形的頂點信息:canvas
其中「1:2-5」表示這是三角形的第一個頂點,這個頂點位於索引是2的圓環上(第三個圓環),這個頂點在圓環中的索引是5(也就是第六個頂點)。
五、輸入PickPoints([[2,5],[3,5],[2,6]],mesh_origin)能夠選定這些頂點
被選中頂點所影響的全部邊框線標示爲黃色,這個「選中」只是改變外觀而已。
六、輸入TransVertex(mesh_origin,arr_ij,BABYLON.Matrix.Translation(0,0,-10))將所選的頂點向z軸負方向移動10,被移動的頂點和前面選中的頂點其實沒有關係,其中arr_ij也能夠直接用索引數組[[2,5],[3,5],[2,6]]代替。
另外一類變形能夠經過輸入:TransVertex(mesh_origin,arr_ij,BABYLON.Matrix.RotationX(Math.PI/2))實現,這能夠把被選中的頂點繞X中旋轉90度。
輸入DeleteMeshes([lines_inpicked])取消被選中的效果,輸入ChangeMaterial(mesh_origin,mat_blue)將邊框換成藍色紋理:
能夠看到變形後的效果,接下來還能夠繼續選擇頂點並變形
七、輸入ExportMesh("1",mat_blue),以txt格式導出babylon模型文件,文件名爲「1.txt」
八、將導出的txt更名爲9.babylon後放入網站目錄中,訪問https://ljzc002.github.io/SnowMiku/HTML/LoadBabylon.html模型預覽頁面,在控制檯輸入ImportMesh("","../ASSETS/SCENE/","9.babylon")便可加載剛纔導出的模型。
2、編程思路:
一、首先要創建一個能夠進行各類測試的基礎場景,使用的代碼以下:
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庫
二、創建一個基礎網格
計劃經過對一個基礎網格進行頂點變換來產生各類各樣的簡單模型,在Babylon.js中「條帶」是一種很是適合頂點變換的網格類型,Babylon.js官方教程中有關於條帶構造和變形的文檔,能夠在這裏下載中文翻譯http://down.51cto.com/data/2449757。
用來創建基礎網格的代碼以下:
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是一個生成圓環路徑的方法,使用者能夠根據須要編寫各類其餘類型的路徑生成方法。
三、調整網格的屬性:
能夠對網格的屬性進行一些調整,可是這些調整隻在這個編輯器裏生效,並不會被導出。
好比對網格材質的調整:
1 function ChangeMaterial(mesh,mat) 2 { 3 mesh.material=mat; 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 }
Babylon.js內置的CreateLineSystem方法能夠方便的創建一個由不少條線段組成的網格。
五、刪除網格
代碼以下:
1 function DeleteMeshes(arr)//假設一個數組裏的都是mesh,完全清空它 2 { 3 var len=arr.length; 4 for(var i=0;i<len;i++) 5 { 6 if(arr[i].dispose) 7 arr[i].dispose(); 8 } 9 arr=[]; 10 }
值得注意的是,直接在控制檯裏執行mesh.dispose()並不生效。
這裏也體現出CreateLineSystem的方便之處——刪除法線時只須要dispose一個對象。
六、鼠標移入時顯示三角形信息
鼠標動做監聽代碼:
1 mesh_origin=new BABYLON.Mesh("mesh_origin",scene);//創建一個空的初始網格對象 2 mesh_surface=new BABYLON.Mesh("mesh_surface",scene); 3 mesh_surface0=new BABYLON.Mesh("mesh_surface0",scene); 4 if(true) 5 {//初始化三個GUI標籤 6 label_index1=new BABYLON.GUI.TextBlock(); 7 label_index1.text = "label_index1"; 8 label_index1.color="white"; 9 label_index1.isVisible=false;//初始化時標籤不可見 10 //label_index1.linkWithMesh(mesh_surface0);//TextBlock並非頂層元素 11 advancedTexture.addControl(label_index1); 12 label_index2=new BABYLON.GUI.TextBlock(); 13 label_index2.text = "label_index2"; 14 label_index2.color="white"; 15 label_index2.isVisible=false; 16 //label_index2.linkWithMesh(mesh_surface0); 17 advancedTexture.addControl(label_index2); 18 label_index3=new BABYLON.GUI.TextBlock(); 19 label_index3.text = "label_index3"; 20 label_index3.color="white"; 21 label_index3.isVisible=false; 22 //label_index3.linkWithMesh(mesh_surface0); 23 advancedTexture.addControl(label_index3); 24 } 25 //監聽鼠標移動 26 canvas.addEventListener("mousemove", function(evt){ 27 var pickInfo = scene.pick(scene.pointerX, scene.pointerY,null,null,camera0);//若是同時有多個激活的相機,則要明確的指出使用哪一個相機 28 //cancelPropagation(evt); 29 //cancelEvent(evt); 30 label_index1.isVisible=false; 31 label_index2.isVisible=false; 32 label_index3.isVisible=false; 33 if(mesh_surface.dispose) 34 { 35 mesh_surface.dispose(); 36 } 37 if(mesh_surface0.dispose) 38 { 39 mesh_surface0.dispose(); 40 } 41 if(pickInfo.hit&&(pickInfo.pickedMesh.name=="mesh_origin"||pickInfo.pickedMesh.name=="mesh_ribbon"))//找到鼠標所在的三角形並重繪之 42 { 43 //在鼠標所指的地方繪製一個三角形,表示被選中的三角形 44 var faceId=pickInfo.faceId; 45 var pickedMesh=pickInfo.pickedMesh; 46 var indices=[pickedMesh.geometry._indices[faceId*3] 47 ,pickedMesh.geometry._indices[faceId*3+1],pickedMesh.geometry._indices[faceId*3+2]]; 48 var vb=pickedMesh.geometry._vertexBuffers; 49 var position=vb.position._buffer._data; 50 var normal=vb.normal._buffer._data; 51 var uv=vb.uv._buffer._data; 52 var len=arr_path[0].length; 53 var p1={index:indices[0],position:[position[indices[0]*3],position[indices[0]*3+1],position[indices[0]*3+2]] 54 ,normal:[normal[indices[0]*3],normal[indices[0]*3+1],normal[indices[0]*3+2]] 55 ,uv:[uv[indices[0]*2],uv[indices[0]*2+1]],ppi:("1:"+(Math.round(indices[0]/len)+0))+"-"+(indices[0]%len)};//pathpointindex 56 var p2={index:indices[1],position:[position[indices[1]*3],position[indices[1]*3+1],position[indices[1]*3+2]] 57 ,normal:[normal[indices[1]*3],normal[indices[1]*3+1],normal[indices[1]*3+2]] 58 ,uv:[uv[indices[1]*2],uv[indices[1]*2+1]],ppi:("2:"+(Math.round(indices[1]/len)+0))+"-"+(indices[1]%len)}; 59 var p3={index:indices[2],position:[position[indices[2]*3],position[indices[2]*3+1],position[indices[2]*3+2]] 60 ,normal:[normal[indices[2]*3],normal[indices[2]*3+1],normal[indices[2]*3+2]] 61 ,uv:[uv[indices[2]*2],uv[indices[2]*2+1]],ppi:("3:"+(Math.round(indices[2]/len)+0))+"-"+(indices[2]%len)}; 62 var po=[(p1.position[0]+p2.position[0]+p3.position[0])/3,(p1.position[1]+p2.position[1]+p3.position[1])/3,(p1.position[2]+p2.position[2]+p3.position[2])/3]; 63 var numm=2; 64 mesh_surface0=newland.make_tryangle(p1,p2,p3,"mesh_surface1",scene);//使用三個點繪製一個三角形 65 mesh_surface0.material=mat_green; 66 mesh_surface0.sideOrientation==BABYLON.Mesh.DOUBLESIDE; 67 mesh_surface0.layerMask=2; 68 label_index1.isVisible=true; 69 //label_index1.layerMask=2;//這個屬性對於gui被管對象並不生效? 70 label_index1.text=p1.ppi;//ppi表示這個頂點是三角形的第幾個頂點,以及這個頂點位於第幾條路徑的第幾個位置 71 var pos1=new BABYLON.Vector3(p1.position[0],p1.position[1],p1.position[2]); 72 label_index1.moveToVector3(pos1,scene); 73 label_index1.itspos=pos1; 74 label_index2.isVisible=true; 75 label_index2.text=p2.ppi; 76 var pos2=new BABYLON.Vector3(p2.position[0],p2.position[1],p2.position[2]); 77 label_index2.moveToVector3(pos2,scene); 78 label_index2.itspos=pos2; 79 label_index3.isVisible=true; 80 label_index3.text=p3.ppi; 81 var pos3=new BABYLON.Vector3(p3.position[0],p3.position[1],p3.position[2]); 82 label_index3.moveToVector3(pos3,scene); 83 label_index3.itspos=pos3; 84 //將三個點移動到場景的中心 85 pt=[(p1.position[0]-po[0])*numm,(p1.position[1]-po[1])*numm,(p1.position[2]-po[2])*numm]; 86 p1.position=pt; 87 pt=[(p2.position[0]-po[0])*numm,(p2.position[1]-po[1])*numm,(p2.position[2]-po[2])*numm]; 88 p2.position=pt; 89 pt=[(p3.position[0]-po[0])*numm,(p3.position[1]-po[1])*numm,(p3.position[2]-po[2])*numm]; 90 p3.position=pt; 91 //在場景的中心再繪製一個大一倍的三角形 92 mesh_surface=newland.make_tryangle(p1,p2,p3,"mesh_surface",scene); 93 mesh_surface.material=mat_green; 94 mesh_surface.sideOrientation==BABYLON.Mesh.DOUBLESIDE; 95 mesh_surface.layerMask=1;//遮罩層次是1 96 97 } 98 99 },false);
須要注意的有如下幾點:
a、鼠標指向時顯示的是這個點在arr_path中的索引,而不是在「頂點數據對象」中的索引!
b、最初的計劃是像魔獸爭霸3同樣在窗口的左下角創建一個遮罩層次爲1的小視口,用來特寫被選中的三角形(mesh_surface),可是後來發現Babylon.js的GUI不能設置遮罩層次,也不能像鼠標選取同樣設置相對於哪個相機,因此取消了左下角的小視口。
若是須要添加視口,代碼以下:
1 var mm = new BABYLON.FreeCamera("minimap", new BABYLON.Vector3(0,0,-20), scene); 2 mm.layerMask = 1; 3 var xstart = 0.0, 4 ystart = 0.1;//意爲佔屏幕寬高的比例 5 var width = 0.2, 6 height = 0.2; 7 //視口邊界從左下角開始 8 mm.viewport = new BABYLON.Viewport( 9 xstart, 10 ystart, 11 width, 12 height 13 ); 14 scene.activeCameras.push(mm);
c、須要注意的是GUI是一次性繪製在視口上的,默認狀況下即便相關的網格位置發生變化,GUI仍然會保持在視口上最初的位置。linkWithMesh方法能夠將GUI和網格綁定起來,可是彷佛只能對部分「底層的」GUI類型生效,爲了使得標籤跟隨頂點移動,須要手動在每一幀渲染以前更新標籤的位置:
1 scene.registerBeforeRender(function() { 2 if(scene.isReady()) 3 { 4 if(label_index1.isVisible==true)//不斷刷新gui的位置 5 {//在具備多個激活相機時選擇了錯誤的參考相機?-》會強制選擇最新創建的相機 6 label_index1.moveToVector3(label_index1.itspos,scene); 7 label_index2.moveToVector3(label_index2.itspos,scene); 8 label_index3.moveToVector3(label_index3.itspos,scene); 9 } 10 } 11 });
七、選取頂點:
3DsMax和Blender(Babylon.js庫在3D模型部分參考了Blender的許多設計)都使用鼠標在網格中選取須要修改的區域,我不是很習慣這種方式,因此考慮用JS函數代替鼠標操做:
1 var lines_inpicked={};//線段系統對象,表示全部被選中點影響的線段 2 var arr_ij=[];//記錄被選中的點在arr_path中的索引的數組 3 /*DeleteMeshes([lines_inpicked]); 4 * PickPoints([[0,0],[0,12],[1,1]],mesh_origin) 5 * */ 6 //選定一些頂點 7 function PickPoints(arr,mesh) 8 { 9 if(arr_path.length==0) 10 { 11 alert("還沒有生成路徑數組!"); 12 return; 13 } 14 if(lines_inpicked.dispose) 15 { 16 lines_inpicked.dispose(); 17 } 18 //arr_ij=[]; 19 //爲了後面考慮,須要先對arr總體遍歷一遍,把首尾接口處關聯起來 20 arr_ij=arr;//把路徑和點的位置記錄下來 21 var vb=mesh.geometry._vertexBuffers; 22 var data_pos=vb.position._buffer._data; 23 var len_pos=data_pos.length; 24 var data_index=mesh.geometry._indices; 25 var len_index=data_index.length; 26 var len=arr.length; 27 var lines=[]; 28 for(var i=0;i<len;i++)//對於每個須要顯示的被選擇點 29 { 30 var int0=arr[i][0]; 31 var int1=arr[i][1]; 32 var vec=arr_path[int0][int1];//獲取到路徑數組中的一個Vector3對象 33 //假設路徑數組和頂點數據是一一對應的?同時假設每一條路徑的長度都和第一條相同 34 var arr_index=[int0*arr_path[0].length+int1];//全部位於所選位置的頂點在頂點數據對象中的索引 35 //下面遍歷網格的繪製索引,找出這個被選中的頂點每一次的使用狀況 36 for(var j=0;j<len_index;j+=3)//根據頂點在三角形中的位置分三種狀況討論 37 {//繪製出和這個頂點相關的全部線段,這樣繪製會有很嚴重的線段重合!觀察是否對性能有很大的影響 38 var len2=arr_index.length; 39 for(var k=0;k<len2;k++) 40 { 41 if(arr_index[k]==data_index[j])//三角形的第一個頂點 42 {//把這個頂點和三角形中的另兩個頂點用黃線連起來 43 var num2=data_index[j+1]*3; 44 var num3=data_index[j+2]*3; 45 var vec2=new BABYLON.Vector3(data_pos[num2],data_pos[num2+1],data_pos[num2+2]); 46 var vec3=new BABYLON.Vector3(data_pos[num3],data_pos[num3+1],data_pos[num3+2]); 47 lines.push([vec,vec2]); 48 lines.push([vec,vec3]); 49 } 50 else if(arr_index[k]==data_index[j+1])//三角形的第一個頂點 51 { 52 var num2=data_index[j]*3; 53 var num3=data_index[j+2]*3; 54 var vec2=new BABYLON.Vector3(data_pos[num2],data_pos[num2+1],data_pos[num2+2]); 55 var vec3=new BABYLON.Vector3(data_pos[num3],data_pos[num3+1],data_pos[num3+2]); 56 lines.push([vec,vec2]); 57 lines.push([vec,vec3]); 58 } 59 else if(arr_index[k]==data_index[j+2])//三角形的第一個頂點 60 { 61 var num2=data_index[j]*3; 62 var num3=data_index[j+1]*3; 63 var vec2=new BABYLON.Vector3(data_pos[num2],data_pos[num2+1],data_pos[num2+2]); 64 var vec3=new BABYLON.Vector3(data_pos[num3],data_pos[num3+1],data_pos[num3+2]); 65 lines.push([vec,vec2]); 66 lines.push([vec,vec3]); 67 } 68 } 69 } 70 } 71 lines_inpicked=new BABYLON.MeshBuilder.CreateLineSystem("lines_normal",{lines:lines,updatable:false},scene); 72 lines_inpicked.color=new BABYLON.Color3(1, 1, 0); 73 } 74 //須要一些選擇選定頂點的方法
有如下幾處須要注意:
a、由於MakeRing生成的路徑裏包括兩個重合的點(首尾),爲了保證變形後的網格仍然連續,在選擇時這兩個重合的點必須同時被選中,通過考慮,規定這個確保首尾重合點同時選中的操做在生成arr數組參數時進行。
b、由於基礎網格mesh_origin的每個頂點剛好位置不一樣,因此一開始我覺得「與被選中的點的位置相同的點」應該具備與被選中點相同的變形效果,可是事實上通過網格變形後,徹底可能出現兩個不相干的頂點位於同一位置的狀況,這時應用相同的變形效果會出現錯誤,好比人能夠把手放在腿上,這時手和腿的接觸點具備相同的位置,但若是所以對這兩個點應用相同的變形效果,就很詭異了。同時如前文所說,有時arr_path中存儲的位置和頂點數據中存儲的位置可能出現微小的誤差。
最後採起的方法是將arr_path中的數組轉化爲頂點數據對象中的數組。
c、這裏直接選取了幾個點進行變形,還須要編寫一些按照某種規則批量選取點的方法,也但願你們能幫我想想怎樣選取頂點比較方便。
八、網格變形:
代碼以下:
1 /* 2 * TransVertex(mesh_origin,arr_ij,BABYLON.Matrix.RotationX(Math.PI/2)) 3 *TransVertex(mesh_origin,arr_ij,BABYLON.Matrix.Translation(0.5,0,0)) 4 * */ 5 //移動選定的頂點,同時更新網格、選取線、法線 6 function TransVertex(mesh,arr,matrix) 7 { 8 var len=arr.length; 9 for(var i=0;i<len;i++)//移動路徑數組裏的每一個頂點 10 { 11 //var vec=arr_path[arr[i][0]][arr[i][1]]; 12 arr_path[arr[i][0]][arr[i][1]]=BABYLON.Vector3.TransformCoordinates(arr_path[arr[i][0]][arr[i][1]],matrix); 13 } 14 //更新網格,發現設置了closePath:true以後在頂點數據裏仍是會自動補上一個接續點,這還不如一開始本身添加!起碼本身添加能夠保證路徑數組和頂點數據的一致性!!!! 15 mesh=BABYLON.MeshBuilder.CreateRibbon(mesh.name,{pathArray:arr_path,updatable:true,instance:mesh,closePath:false,closeArray:false}); 16 //上面的更新重繪是默認重算法線方向的,這與直接修改頂點數據不一樣 17 //清空法線 18 DeleteMeshes([lines_normal]); 19 //更新選取頂點表示 20 PickPoints(arr,mesh); 21 } 22 //須要一些生成變化矩陣的方法
其實這裏移動的頂點和前面選擇的點沒有關係,不選擇也能夠直接變形,只不過不會有黃線顯示罷了。這裏也一樣須要一些生成更復雜的變換矩陣的方法。
九、導出:
導出方法的調用以下:
1 /*ExportMesh("1",mat_blue)*/ 2 function ExportMesh(filename,mat) 3 { 4 try{ 5 newland.ExportBabylonMesh([mesh_origin],filename,mat); 6 } 7 catch(e) 8 { 9 console.error(e) 10 } 11 }
函數的第一個參數是導出後的文件名(不包括擴展名),第二個參數是網格使用的材質對象(暫時只支持簡單的顏色材質)。
其中ExportBabylonMesh是我編寫的newland庫中的一個方法,這個方法在前面關與3D模型的文章中也有提到過(https://www.cnblogs.com/ljzc002/p/6884252.html),這個方法總體上沒有太大的改變。可是在舊版的Babylon.js中,頂點數據對象中的data屬性是數組類型(?),而在新版中data屬性則是typedArray類型,雖然這兩種數據類型看起來很像,但在使用JSON.stringify(arr)轉化爲JSON字符串時,對於一樣的數據[1,2,3],前者會轉化爲"[1,2,3]"後者則是"{"0":1,"1":2,"2":3}"!
在導入模型文件時後者會報錯:「attempt to access out of range vertices in attribute 0」,這意味着在導入模型時頂點數據數組沒有正確的載入,個人解決方案是導出前將data屬性強制轉化爲數組類型:
1 //將TypedArray轉化爲普通array 2 newland.BuffertoArray2=function(arr) 3 { 4 var arr2=[]; 5 var len=arr.length; 6 for(var i=0;i<len;i++) 7 { 8 arr2.push(arr[i]); 9 } 10 return arr2; 11 }
對於舊版的Babylon.js應該能夠不加修改的使用這個方法,由於typedArray也具備數組的大部分功能。
十、導入:
在另外一個頁面裏實現模型導入功能,這個頁面一樣在「基礎場景」的基礎上編寫,其中導入方法以下:
1 function ImportMesh(objname,filepath,filename) 2 { 3 4 BABYLON.SceneLoader.ImportMesh(objname, filepath, filename, scene 5 , function (newMeshes, particleSystems, skeletons) 6 {//載入完成的回調函數 7 if(mesh_origin&&mesh_origin.dispose) 8 { 9 mesh_origin.dispose(); 10 } 11 mesh_origin=newMeshes[0]; 12 //mesh_origin.material=mat_frame; 13 //mesh_origin.layerMask=2; 14 } 15 ); 16 }
20180807修改
使用上述方法創建了一個「農夫山泉4L裝塑料桶」的網格,能夠經過http://ljzc002.github.io/SnowMiku/HTML/NongFuSpring.html訪問。在新增的Make.js文件中編寫了幾個生成所需頂點路徑的方法,包括:生成正多邊形路徑、生成指定軸對稱路徑、生成圓弧表面;以及調整已有頂點位置的方法,包括:產生溝壑、按正弦曲線規律變換頂點路徑。
最終產生的網格以下圖:
實踐證實,對於具備必定排布規律的多頂點網格來講,使用編程方法產生網格比較方便。
20180808修改
經過查看文檔,發現雖然高級動態紋理在創建時沒法指定在哪一個相機中進行顯示,但他的屬性支持多相機設置,須要設置的屬性是advancedTexture.layer.layerMask!