使用骨骼動畫技術能夠將網格的頂點分配給若干骨頭,經過給骨頭設定關鍵幀和父子關係,能夠賦予網格高度動態並具備傳遞性的變形 效果。這裏結合以前的相關研究在網頁端使用JavaScript實現了一個簡單的骨骼動畫編輯和模型生成工具。 css
1、顯示效果:html
一、訪問https://ljzc002.github.io/Bones/HTML/CstestSpaceCraft2.html查看測試頁面:git
屏幕右側的Babylon.js場景中是一個初始網格。github
二、在Chrome瀏覽器控制檯輸入「ImportMesh("","../ASSETS/SCENE/","SpaceCraft.babylon")」,載入以前編寫的一個宇宙飛船模型,關於這個模型的編寫方式能夠參考https://www.cnblogs.com/ljzc002/p/9473438.htmlweb
三、點擊「新增骨骼」按鈕,會在左側創建一個可摺疊的骨頭編輯區(標籤的類名是div_flexible),一個編輯區分爲六行,每行包括四個文本框。算法
四、在第一行的四個文本框中輸入-一、0、0、0,點擊這個編輯區的刷新按鈕,將在場景中創建一個朝向(-1,0,0)方向距原點距離爲0的平面,全部包含平面正面(或平面上)頂點的線會被標爲綠色(「正面」能夠理解爲從平面出發,沿着平面法線方向移動能夠到達這個頂點,數學上能夠說「這個頂點到平面的距離爲正」)canvas
當頂點的數量較多時,上述計算會花費必定時間,控制檯裏會打印出當前的查找進度。數組
在編輯區的第二行輸入0、一、0、-3(表示沿法線的反方向到原點的距離爲3)會創建另外一個平面,同時處於兩個平面正面的頂點會被選中,最多能夠創建6個這樣的區分平面。瀏覽器
五、選定這些頂點做爲一號骨頭後,點擊「編輯關鍵幀」按鈕將打開一號骨頭的關鍵幀編輯對話框架構
其中父骨骼索引設爲0號骨骼,0號骨骼能夠理解爲模型的原點,在整個動畫過程當中保持不變;關節點座標由三個文本框組成,表示這一塊骨頭和父骨頭的鏈接點的位置,這裏一號骨頭的關節點設爲(0,0,0)。下面的文本框裏是表示關鍵幀矩陣的腳本,解讀規則爲「幀數@矩陣對象#幀數@矩陣對象」,其中的ms.xx是簡寫的Babylon.js矩陣構造函數,其對應關係以下:(代碼位於CookBones.js文件中)
1 //在這裏寫對關鍵幀腳本的處理和骨骼模型導出 2 //定義一種簡單的腳本簡化輸入 3 var ms={}//MatrixScript 4 ms.rx=function(rad)//繞x軸旋轉 5 { 6 return BABYLON.Matrix.RotationX(rad); 7 } 8 ms.ry=function(rad)//繞y軸旋轉 9 { 10 return BABYLON.Matrix.RotationY(rad); 11 } 12 ms.rz=function(rad)//繞z軸旋轉 13 { 14 return BABYLON.Matrix.RotationZ(rad); 15 } 16 ms.m1=function(){//生成一個單位陣 17 return BABYLON.Matrix.Identity(); 18 } 19 ms.sc=function(x,y,z)//縮放,由於作了矩陣標準化,在如今的場景裏縮放不會起做用!! 20 { 21 return BABYLON.Matrix.Scaling(x,y,z); 22 } 23 ms.tr=function(x,y,z)//位移 24 { 25 return BABYLON.Matrix.Translation(x,y,z); 26 } 27 //0@ms.m1()#120@ms.rx(2)#240@ms.m1() 28 ms.fa=function(arr)//從數組生成矩陣 29 { 30 return BABYLON.Matrix.FromArray(arr); 31 } 32 33 var vs={}//VectorScript 34 vs.tr=function(vec3,matrix)//對向量進行矩陣變化 35 { 36 return BABYLON.Vector3.TransformCoordinates(vec3.clone(),matrix); 37 } 38 var pi=Math.PI;
點擊「寫入初始關鍵幀」,則關鍵幀設置被保存,同時編輯區上的複選框會被選中。
六、再給宇宙飛船的翅膀設置骨骼:
設置關鍵幀
由於是取z值小於等於-6的頂點組成骨頭2,因此將關節點位置設爲(0,0,-6),固然,也能夠把關節點設在其餘位置,如過這樣作翅膀的運動方式將有所不一樣。
對稱的設置右側的翅膀,生成骨頭3。
七、設置完成後,點擊「預覽模型」按鈕,將在場景的x方向顯示骨骼動畫效果:
這裏體現出當前工具的一個缺點:還沒有容許同一頂點綁定多塊骨頭,對於重複選取的頂點,後設置的骨頭會覆蓋以前的設置。
由於博客園對圖片大小有限制,只能截取骨骼動畫的一部分。
點擊「導出模型」則能夠文本方式導出上述含有骨骼動畫的模型。
八、父骨頭對子骨頭的影響:
能夠訪問https://ljzc002.github.io/Bones/HTML/Cstest2.html頁面,刷新並編輯三塊骨頭的關鍵幀能夠看到骨骼動畫的傳遞效果。
2、代碼實現
一、工程結構:
其中ClickButton.js裏是除矩陣計算外全部和按鈕響應相關的代碼,ComputeMatrix.js裏是全部和矩陣計算有關的代碼,Flex.js沒用。
二、html2D網頁繪製(並非重點)。
html文件:(其中包括創建一個基礎Babylon.js場景的js代碼)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>載入宇宙飛船模型進行測試</title> 6 <link href="../CSS/newland.css" rel="stylesheet"> 7 <link href="../CSS/stat.css" rel="stylesheet"> 8 <script src="../JS/LIB/babylon.40v.all.max.js"></script> 9 <script src="../JS/LIB/stat.js"></script> 10 <script src="../JS/MYLIB/Events.js"></script> 11 <script src="../JS/MYLIB/FileText.js"></script> 12 <script src="../JS/MYLIB/newland.js"></script> 13 <script src="../JS/MYLIB/View.js"></script> 14 <script src="../JS/MYLIB/Att7.js"></script> 15 <!--script src="../JS/MYLIB/ExportBabylonBones2.js"></script--> 16 <script src="../JS/PAGE/ClickButton.js"></script> 17 <script src="../JS/PAGE/ComputeMatrix.js"></script> 18 <script src="../JS/PAGE/CookBones.js"></script> 19 <style> 20 .div_flexible{float:left;width:100%; } 21 .div_flextop{width:100%;height:36px;background-color: #15428B;float: left} 22 .floatleft{float:left;margin-left: 10px;margin-top:6px} 23 .div_flexbottom{width:270px;margin-left:5px;height: 300px;display: none;overflow: hidden;border:1px solid #15428B;float: left} 24 .div_flexcell{float:left;height:50px;width:100%} 25 .div_key{} 26 </style> 27 </head> 28 <body oncontextmenu="return false;"> 29 <!--https://ljzc002.github.io/Bones/HTML/Cstest2.html--> 30 <div style="position:absolute;top: 0px;left:0px;width:300px;height: 100%;overflow: hidden;"> 31 <button style="float: left;margin-top:10px;margin-left: 10px" onclick="addBone()">新增骨骼</button> 32 <button style="float: left;margin-top:10px;margin-left: 10px" onclick="ExportMesh(obj_scene,0)">預覽模型</button> 33 <button style="float: left;margin-top:10px;margin-left: 10px" onclick="ExportMesh(obj_scene,1)">導出模型</button> 34 <div style="position:absolute;top:50px;overflow-y: scroll;width:300px;height:500px" id="div_flexcontainer"> 35 <!--這之內的內容都是可複製粘貼的--> 36 <div class="div_flexible" number="1"> 37 <div class="div_flextop"> 38 <span class="floatleft str_flexlen" style="color:darkgoldenrod;font-size: 16px;width:32px">1</span> 39 <button class="floatleft" onclick="flex()">收縮</button> 40 <button class="floatleft" onclick="OpenDivKey()">編輯關鍵幀</button> 41 <input class="floatleft checkbone" style="width: 20px;height: 20px;" type="checkbox"> 42 <!--勾選表示導出骨骼時包含這一塊,不然不導出它-》考慮到可能存在的複雜層級關係,決定導出全部骨頭--> 43 <button class="floatleft" onclick="ShowClip2()">刷新</button> 44 </div> 45 <div class="div_flexbottom" style="display: block;"> 46 <!--在這裏設置最多六個切割平面--> 47 <div class="div_flexcell" number="1"> 48 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 49 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 50 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 51 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 52 <div class="div_comment" style="display: none;">[-1,0,0,0]</div></div> 53 <div class="div_flexcell" number="2"> 54 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 55 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 56 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 57 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 58 </div> 59 <div class="div_flexcell" number="3"> 60 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 61 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 62 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 63 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 64 </div> 65 <div class="div_flexcell" number="4"> 66 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 67 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 68 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 69 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 70 </div> 71 <div class="div_flexcell" number="5"> 72 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 73 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 74 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 75 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 76 </div> 77 <div class="div_flexcell" number="6"> 78 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 79 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 80 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 81 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 82 </div> 83 </div> 84 <div class="div_comment0" style="display: none;">{"str_indexp":0,"str_posjx":0,"str_posjy":0,"str_posjz":0,"text_key":"0@ms.m1()#30@ms.rz(0.5)#60@ms.m1()#90@ms.rz(-0.5)#120@ms.m1()#150@ms.rz(0.5)#180@ms.m1()#210@ms.rz(-0.5)#240@ms.m1()"}</div></div> 85 <!--複製粘貼的截止線--> 86 </div> 87 </div> 88 <div id="div_allbase" style="position:absolute;top: 0px;right: 0px;left:301px;height: 100%"> 89 <canvas id="renderCanvas"></canvas> 90 <div id="fps" style="z-index: 301;"></div> 91 </div> 92 <div id="div_hiden" style="display: none"> 93 <div class="div_hidecell"> 94 <div class="div_flexible" number="1"> 95 <div class="div_flextop"> 96 <span class="floatleft str_flexlen" style="color:darkgoldenrod;font-size: 16px;width:32px">1</span> 97 <button class="floatleft" onclick="flex()">展開</button> 98 <button class="floatleft" onclick="OpenDivKey()" disabled="disabled">編輯關鍵幀</button> 99 <input class="floatleft checkbone" style="width: 20px;height: 20px;" type="checkbox"> 100 <!--勾選表示導出骨骼時包含這一塊,不然不導出它-》考慮到可能存在的複雜層級關係,決定導出全部骨頭--> 101 <button class="floatleft" onclick="ShowClip2()">刷新</button> 102 </div> 103 <div class="div_flexbottom"> 104 <!--在這裏設置最多六個切割平面--> 105 <div class="div_flexcell" number="1"> 106 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 107 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 108 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 109 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 110 </div> 111 <div class="div_flexcell" number="2"> 112 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 113 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 114 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 115 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 116 </div> 117 <div class="div_flexcell" number="3"> 118 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 119 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 120 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 121 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 122 </div> 123 <div class="div_flexcell" number="4"> 124 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 125 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 126 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 127 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 128 </div> 129 <div class="div_flexcell" number="5"> 130 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 131 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 132 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 133 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 134 </div> 135 <div class="div_flexcell" number="6"> 136 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 137 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 138 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 139 <input class="floatleft" style="width:50px" onchange="ShowClip()"> 140 </div> 141 </div> 142 </div> 143 </div> 144 <div class="div_hidecell"> 145 <div class="div_key"><!--它的樣式由open_div設定了--> 146 <span class="floatleft"></span><br> 147 <span class="floatleft ">父骨骼索引:</span><input class="floatleft str_indexp" value="0"> 148 <!--span class="floatleft ">每秒幀數:</span><input class="floatleft str_fps" value="30"--><br> 149 <span class="floatleft ">關節點座標:</span><input class="floatleft str_posjx" value="0"><input class="floatleft str_posjy" value="0"><input class="floatleft str_posjz" value="0"> 150 <button class="floatleft" onclick="InsertKey()">寫入初始關鍵幀</button> 151 <!--在這裏使用一種格式化的文本,體現關鍵幀與矩陣,num_key@matrix#--> 152 <textarea class="floatleft text_key" style="width:90%;top:40px;height: 250px;"></textarea> 153 <button class="floatleft" onclick="delete_div('div_open');delete_div('div_mask');">取消</button> 154 </div> 155 </div> 156 </div> 157 </body> 158 <script> 159 var VERSION=1.0,AUTHOR="lz_newland@163.com"; 160 var machine,canvas,engine,scene,gl,MyGame={}; 161 canvas = document.getElementById("renderCanvas"); 162 engine = new BABYLON.Engine(canvas, true); 163 engine.displayLoadingUI(); 164 gl=engine._gl;//決定在這裏結合使用原生OpenGL和Babylon.js; 165 scene = new BABYLON.Scene(engine); 166 var divFps = document.getElementById("fps"); 167 window.onload=beforewebGL; 168 function beforewebGL() 169 { 170 if(engine._webGLVersion==2.0)//輸出ES版本 171 { 172 console.log("ES3.0"); 173 } 174 else{ 175 console.log("ES2.0"); 176 } 177 //MyGame=new Game(0,"first_pick","","http://127.0.0.1:8082/"); 178 /*0-startWebGL 179 * */ 180 webGLStart(); 181 } 182 //從下面開始分紅簡單測試和對象框架兩種架構 183 //全局對象 184 var light0//全局光源 185 ,camera0//主相機 186 ,arr_bone;//除了根骨骼以外全部骨骼的集合 187 var obj_scene=null; 188 var num_fps=30;//一開始設置好動畫幀數和總幀數 189 var sum_frame=241;//實際上是包括0到240的241幀 190 var mesh_origin=null; 191 function webGLStart() 192 { 193 window.addEventListener("resize", function () { 194 engine.resize(); 195 }); 196 if(true) 197 { 198 camera0 =new BABYLON.FreeCamera("FreeCamera", new BABYLON.Vector3(0, 0, -80), scene); 199 camera0.attachControl(canvas, true); 200 camera0.speed=0.5; 201 camera0.minZ=0.01;//問題出在這裏!!設置的太小,會致使鼠標pick失敗!!!! 202 light0 = new BABYLON.HemisphericLight("Hemi0", new BABYLON.Vector3(0, 1, 0), scene); 203 light0.groundColor=new BABYLON.Color3(0.5,0.5,0.5); 204 light0.specular = new BABYLON.Color3(1, 1, 1); 205 light0.diffuse = new BABYLON.Color3(1, 1, 1); 206 var advancedTexture=BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("ui1"); 207 var mat_green = new BABYLON.StandardMaterial("mat_green", scene); 208 mat_green.diffuseColor = new BABYLON.Color3(0, 1, 0); 209 mat_green.backFaceCulling=false; 210 var mesh_base=new BABYLON.MeshBuilder.CreateSphere("mesh_base",{diameter:1},scene); 211 mesh_base.material=mat_green; 212 mesh_base.position.x=0; 213 //mesh_base.layerMask=2; 214 var mesh_base1=new BABYLON.MeshBuilder.CreateSphere("mesh_base1",{diameter:1},scene); 215 mesh_base1.position.y=10; 216 mesh_base1.position.x=0; 217 mesh_base1.material=mat_green; 218 //mesh_base1.layerMask=2; 219 var mesh_base2=new BABYLON.MeshBuilder.CreateSphere("mesh_base2",{diameter:1},scene); 220 mesh_base2.position.y=-10; 221 mesh_base2.position.x=0; 222 mesh_base2.material=mat_green; 223 //mesh_base2.layerMask=2; 224 225 mat_frame = new BABYLON.StandardMaterial("mat_frame", scene); 226 mat_frame.wireframe = true; 227 mat_frame.freeze(); 228 229 mat_alpha_yellow=new BABYLON.StandardMaterial("mat_alpha_yellow", scene); 230 mat_alpha_yellow.diffuseColor = new BABYLON.Color3(1,1,0); 231 mat_alpha_yellow.alpha=0.2;//不透明度 232 mat_alpha_yellow.freeze(); 233 234 mat_red=new BABYLON.StandardMaterial("mat_red", scene); 235 mat_red.diffuseColor = new BABYLON.Color3(1,0,0); 236 mat_red.wireframe=true; 237 mat_red.freeze(); 238 } 239 240 //在這裏設置一個初始的默認網格, 241 mesh_origin=new BABYLON.MeshBuilder.CreateSphere("mesh_origin",{diameter:8,diameterY:64,segments:16},scene); 242 mesh_origin.material=mat_frame; 243 var vb=mesh_origin.geometry._vertexBuffers; 244 var data_pos=vb.position._buffer._data; 245 var len_pos=data_pos.length; 246 mesh_origin.matricesIndices=newland.repeatArr([0],len_pos/3); 247 mesh_origin.matricesWeights=newland.repeatArr([1,0,0,0],len_pos/3); 248 mesh_origin.skeletonId=0; 249 obj_scene=newland.CreateObjScene(); 250 newland.AddMesh2Model(obj_scene,mesh_origin,"mesh_origin2"); 251 newland.AddSK2Model(obj_scene,"sk_test1");//向模型中添加骨骼 252 var bone={ 253 'animation':{ 254 dataType:3, 255 framePerSecond:num_fps, 256 keys:[], 257 loopBehavior:1, 258 name:'_bone'+0+'Animation', 259 property:'_matrix' 260 }, 261 'index':0, 262 'matrix':BABYLON.Matrix.Identity().toArray(), 263 'name':'_bone'+0, 264 'parentBoneIndex':-1 265 }; 266 //bone. 267 newland.ExtendKeys(bone,sum_frame);//初始擴展根骨骼的關鍵幀,認爲根骨骼是一直保持不變的 268 newland.AddBone2SK(obj_scene,0,bone);// 向骨骼中添加骨頭 269 arr_bone=obj_scene.skeletons[0].bones; 270 BABYLON.Animation.AllowMatricesInterpolation = true;//動畫矩陣插值 271 //創建兩個gui顯示進度,可是這樣是不行的,由於此時主線程已經阻塞了,gui是不會刷新的!! 272 /*if(true) 273 { 274 var UiPanel2 = new BABYLON.GUI.StackPanel(); 275 UiPanel2.width = "220px"; 276 UiPanel2.height="30px"; 277 UiPanel2.fontSize = "14px"; 278 UiPanel2.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT; 279 UiPanel2.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP 280 UiPanel2.color = "white"; 281 UiPanel2.background = "green"; 282 advancedTexture.addControl(UiPanel2); 283 text1 = new BABYLON.GUI.TextBlock(); 284 text1.text = "0"; 285 text1.background = "blue"; 286 text1.color="white"; 287 text1.height="30px"; 288 text1.width="30px"; 289 text1.left="0px"; 290 UiPanel2.addControl(text1); 291 text2 = new BABYLON.GUI.TextBlock(); 292 text2.color="white"; 293 text2.text = "/1"; 294 text2.height="30px"; 295 UiPanel2.addControl(text2); 296 }*/ 297 298 reInit();//若是dom內容都是粘貼過來的,須要從新初始化一下arr_bone,至關於從新執行addbone 299 ImportMesh("","../ASSETS/SCENE/","SpaceCraft.babylon") 300 MyBeforeRender(); 301 } 302 function MyBeforeRender() 303 { 304 scene.registerBeforeRender(function() { 305 if(scene.isReady()) 306 { 307 } 308 }); 309 engine.runRenderLoop(function () { 310 engine.hideLoadingUI(); 311 if (divFps) { 312 // Fps 313 divFps.innerHTML = engine.getFps().toFixed() + " fps"; 314 } 315 scene.render(); 316 }); 317 } 318 /* 319 * ImportMesh("","../../ASSETS/SCENE/","10.babylon") 320 * ImportMesh("","../ASSETS/SCENE/","SpaceCraft.babylon") 321 * */ 322 function ImportMesh(objname,filepath,filename) 323 { 324 325 BABYLON.SceneLoader.ImportMesh(objname, filepath, filename, scene 326 , function (newMeshes, particleSystems, skeletons) 327 {//載入完成的回調函數 328 newland.ClearMeshinModel(obj_scene); 329 if(mesh_origin&&mesh_origin.dispose) 330 { 331 mesh_origin.dispose(); 332 } 333 mesh_origin=newMeshes[0]; 334 mesh_origin.material=mat_frame; 335 //mesh_origin.layerMask=2; 336 var vb=mesh_origin.geometry._vertexBuffers; 337 var data_pos=vb.position._buffer._data; 338 var len_pos=data_pos.length; 339 mesh_origin.matricesIndices=newland.repeatArr([0],len_pos/3); 340 mesh_origin.matricesWeights=newland.repeatArr([1,0,0,0],len_pos/3); 341 mesh_origin.skeletonId=0; 342 newland.AddMesh2Model(obj_scene,mesh_origin,"mesh_origin2"); 343 } 344 ); 345 } 346 </script> 347 </html>
css文件:
1 /*通用屬性*/ 2 body{ margin: 0; padding: 0; border: 0; text-align: center; overflow: hidden;width: 100%; 3 height: 100%;position: fixed; font-family: verdana,arial,sans-serif; touch-action: none; 4 -ms-touch-action: none;font-size: 12px;min-width: 600px;} 5 ul { list-style: none; margin: 0; padding: 0;} 6 li{ list-style: none; margin: 0; padding: 0;} 7 ul li { float: left;} 8 button{ cursor: pointer; height: 23px;} 9 a:link{ text-decoration: none;} 10 11 /*頂層屬性*/ 12 13 #div_control{height: 100%;width:100%;background-color: transparent;z-index: 100;position: absolute;top: 0px;left: 0px;pointer-events: none;} 14 #renderCanvas { width: 100%; height: 100%; outline: none;} 15 .div_col{width: 80px;height: 26px;padding: 5px;overflow: visible;position: relative;pointer-events: none;} 16 .to_left{float: left;text-align: left} 17 .to_right{float: right;text-align: right} 18 .btn_first{text-align: center;width: 60px;pointer-events:auto} 19 .btn_second{text-align: center;overflow: visible;overflow-wrap: normal;display: block;position: absolute;pointer-events:auto} 20 .hidden{display: none} 21 .btn_third{text-align: center;display: block;pointer-events:auto} 22 .div_mask{ height: 100%; width: 100%; display:block; z-index: 400; background: #cccccc; position: absolute; 23 left: 0px; top: 0px; filter: alpha(opacity=40); opacity: 0.40; overflow: hidden;} 24 25 /*彈出層的一些屬性*/ 26 .div_cook2{ margin-top: 5px; margin-left: 0px; margin-right: auto; width: 100%; height: 35px; font-family: 宋體; font-size: 12px; float: left;} 27 .div_cook2 ul{float: left;margin-top: 5px;margin-bottom: 15px} 28 .div_cook2 li{ float: left; /*margin-top: 10px;*/ margin-left: 5px; margin-right: 10px; /*color:darkred;*/ color:#020202;} 29 .div_cook2 span{ width: 120px; float: left; text-align: right; /*color: darkred;*/ color: #15428b; padding-right: 5px; font-weight: bold;} 30 .div_cook2 button{ float: left; margin-right: 5px; margin-left: 5px; width: 52px; height: 20px; text-align: center; background-color: #d6e7ef; border: solid 1px #020202;} 31 .btn_close{ float:right;position:static; width: 14px;height: 14px; margin: 0;margin-top: 2px;margin-right:2px;padding: 0 32 ;background: url(../ASSETS/IMAGE/close.png) no-repeat;border: 0px;vertical-align:top;z-index: 101;} 33 .str_number{ border: solid 1px #020202; width: 60px;} 34 .str_normal{ border: solid 1px #020202; width: 120px;} 35 .str_date{ width: 120px;} 36 .str_text{ border: solid 1px #020202; width:220px ; } 37 #fps { position: absolute; right: 20px; top: 5em; font-size: 20px; color: white;/*幀數顯示*/ 38 text-shadow: 2px 2px 0 black;} 39 40 41 /*表格的屬性*/ 42 #all_base{min-height: 400px;min-width: 250px;height: 100%;width:100%;position: relative;overflow-x:hidden;overflow-y: hidden;} 43 td input{ height: 100%; width: 100%; border:0; text-align: center; background-color: inherit;} 44 .div_tab{float: left;position: relative;width:4000px;overflow-x: hidden;overflow-y: scroll} 45 .div_tab td{ text-align: center; /*border: solid 1px #15428B;*/ border-right:solid 1px #15428B; border-bottom: solid 1px #15428B; 46 line-height: 16px; font-size: 13px; height: 24px; padding: 1px; background-color: inherit; word-break: keep-all; 47 /*display: inline-block*/} 48 .div_tab th{ text-align: center; /*border: solid 1px #15428B;*/ line-height: 16px; font-size: 13px; height: 36px; 49 padding: 1px; text-align: center; border-right: solid 1px #15428B; border-bottom: solid 1px #15428B; word-break: keep-all; 50 white-space:nowrap; overflow: hidden; text-overflow: ellipsis;/*display: inline-block*/} 51 .div_tab table{ float: left; width: auto; border-right-width:0px; border: solid 1px #15428B; table-layout: fixed;} 52 .div_tab tr{ width: auto; vertical-align: middle; /*border: solid 1px #15428B;*/ padding: 1px;} 53 td a{ cursor: pointer;} 54 td button{ cursor: pointer;} 55 .div_mask2{ display:block; left: 0px; top: 0px; /*filter: alpha(opacity=50); opacity: 0.50;*/ overflow: hidden;/*鎖定的表頭表列*/ 56 position: absolute; float: left; overflow-x: hidden} 57 table{ border-spacing:0;} 58 .div_mask2 td{ text-align: center; /*border: solid 1px #15428B;*/ border-right:solid 1px #15428B; border-bottom: solid 1px #15428B; 59 line-height: 16px; font-size: 13px; height: 24px; padding: 1px; background-color: inherit; word-break: keep-all;} 60 .div_mask2 th{ text-align: center; /*border: solid 1px #15428B;*/ line-height: 16px; font-size: 13px; height: 36px; 61 padding: 1px; text-align: center; border-right: solid 1px #15428B; border-bottom: solid 1px #15428B; word-break: keep-all; 62 white-space:nowrap; overflow: hidden; text-overflow: ellipsis;} 63 .div_mask2 table{ float: left; width: auto; border-right-width:0px; border: solid 1px #15428B; table-layout: fixed; 64 position: absolute;} 65 .div_mask2 tr{ width: auto; vertical-align: middle; /*border: solid 1px #15428B;*/ padding: 1px;} 66 .combo-panel li{ float:none;} 67 .btn_limlen{ /*float: left;*/ height: 20px; width: 20px; border: 1px solid; /*margin-top: 6px;*/ /*margin-left: 4px;*/ 68 background: url(../ASSETS/IMAGE/play.png) no-repeat; position: absolute; -moz-border-radius: 3px; /* Gecko browsers圓角 */ 69 -webkit-border-radius: 3px; /* Webkit browsers */ border-radius:3px; /* W3C syntax */ position: absolute; 70 top: 6px; right: 4px;} 71 72 /*幀數顯示*/ 73 #fps { 74 position: absolute; 75 right: 20px; 76 top: 5px; 77 font-size: 20px; 78 color: white; 79 text-shadow: 2px 2px 0 black; 80 } 81 82 #stats { 83 position: absolute; 84 right: 20px; 85 top: 11em; 86 font-size: 14px; 87 color: white; 88 text-align: right; 89 text-shadow: 2px 2px 0 black; 90 } 91 92 #status { 93 position: absolute; 94 left: 20px; 95 bottom: 20px; 96 font-size: 14px; 97 color: white; 98 text-shadow: 2px 2px 0 black; 99 }
控制編輯區展縮的JavaScript代碼:(位於ClickButton.js文件中)
1 //放和左側伸縮菜單相關的內容 2 var flex_current=null;//當前展開的flex 3 function flex()//展縮一塊骨骼的配置菜單 4 { 5 var evt=evt||window.event||arguments[0]; 6 cancelPropagation(evt); 7 var obj=evt.currentTarget?evt.currentTarget:evt.srcElement;//obj是展縮按鈕 8 if(obj.innerHTML=="展開") 9 { 10 11 var divs=document.querySelectorAll(".div_flexbottom");//要把其餘展開狀態的都關掉,同時還要改變高亮頂點(或者邊線)狀態 12 var len=divs.length; 13 for(var i=0;i<len;i++) 14 { 15 divs[i].style.display="none"; 16 divs[i].parentNode.querySelectorAll("button")[0].innerHTML="展開"; 17 divs[i].parentNode.querySelectorAll("button")[1].disabled="disabled"; 18 } 19 obj.innerHTML="收縮"; 20 obj.parentNode.parentNode.querySelectorAll(".div_flexbottom")[0].style.display="block"; 21 obj.parentNode.querySelectorAll("button")[1].disabled=null; 22 ClearAllClip(); 23 if(lines_inpicked&&lines_inpicked.dispose) 24 { 25 lines_inpicked.dispose(); 26 } 27 flex_current=obj.parentNode.parentNode; 28 var divs=flex_current.querySelectorAll(".div_flexcell");//根據可能存在的初始值初始化文本框,可是還須要手動點擊每一個骨骼的刷新按鈕 29 var len2=divs.length; 30 for(var i=0;i<len2;i++) 31 { 32 var div_comment=divs[i].querySelectorAll(".div_comment")[0]; 33 if(div_comment)//若是這個平面有記錄的數據 34 { 35 var arr=JSON.parse(div_comment.innerHTML); 36 var inputs=divs[i].querySelectorAll("input"); 37 inputs[0].value=arr[0]; 38 inputs[1].value=arr[1]; 39 inputs[2].value=arr[2]; 40 inputs[3].value=arr[3]; 41 } 42 } 43 } 44 else if(obj.innerHTML=="收縮") 45 { 46 obj.innerHTML="展開"; 47 obj.parentNode.parentNode.querySelectorAll(".div_flexbottom")[0].style.display="none"; 48 obj.parentNode.querySelectorAll("button")[1].disabled="disabled"; 49 ClearAllClip(); 50 if(lines_inpicked&&lines_inpicked.dispose) 51 { 52 lines_inpicked.dispose(); 53 } 54 } 55 }
三、模型對象的初始化:
Babylon.js格式模型的層次結構能夠參考https://www.cnblogs.com/ljzc002/p/8927221.html
a、在場景中創建一個用來導出3D模型的對象:
創建這個對象的方法在newland.js文件中:
1 //返回一個最簡單的Babylon.js場景格式 2 newland.CreateObjScene=function() 3 { 4 var obj_scene= 5 { 6 'autoClear': true, 7 'clearColor': [0,0,0], 8 'ambientColor': [0,0,0], 9 'gravity': [0,-9.81,0], 10 'cameras':[], 11 'activeCamera': null, 12 'lights':[], 13 'materials':[], 14 'geometries': {}, 15 'meshes': [], 16 'multiMaterials': [], 17 'shadowGenerators': [], 18 'skeletons': [], 19 'sounds': [] 20 }; 21 return obj_scene; 22 }
b、向模型中添加一個網格:
將網格對象的各類屬性交給模型對象
1 //向場景格式中加入一個網格對象 2 newland.AddMesh2Model=function(obj_scene,mesh,name) 3 { 4 var obj_mesh={}; 5 obj_mesh.name=name?name:mesh.name;//防止在本頁面加載致使網格重名 6 obj_mesh.id=name?name:mesh.id; 7 //obj_mesh.materialId=mat.id;//爲避免出現重名材質,先不添加這個屬性 8 obj_mesh.position=[mesh.position.x,mesh.position.y,mesh.position.z]; 9 obj_mesh.rotation=[mesh.rotation.x,mesh.rotation.y,mesh.rotation.z]; 10 obj_mesh.scaling=[mesh.scaling.x,mesh.scaling.y,mesh.scaling.z]; 11 obj_mesh.isVisible=true; 12 obj_mesh.isEnabled=true; 13 obj_mesh.checkCollisions=false; 14 obj_mesh.billboardMode=0; 15 obj_mesh.receiveShadows=true; 16 obj_mesh.metadata=mesh.metadata; 17 if(mesh.matricesIndices) 18 { 19 obj_mesh.matricesIndices=mesh.matricesIndices; 20 obj_mesh.matricesWeights=mesh.matricesWeights; 21 obj_mesh.skeletonId=mesh.skeletonId; 22 } 23 if(mesh.geometry)//是有實體的網格 24 { 25 var vb=mesh.geometry._vertexBuffers; 26 obj_mesh.positions=newland.BuffertoArray2(vb.position._buffer._data); 27 obj_mesh.normals=newland.BuffertoArray2(vb.normal._buffer._data); 28 obj_mesh.uvs= newland.BuffertoArray2(vb.uv._buffer._data); 29 obj_mesh.indices=newland.BuffertoArray2(mesh.geometry._indices); 30 obj_mesh.subMeshes=[{ 31 'materialIndex': 0, 32 'verticesStart': 0, 33 'verticesCount': mesh.geometry._vertexBuffers.position._buffer._data.length,//mesh.geometry._totalVertices, 34 'indexStart': 0, 35 'indexCount': mesh.geometry._indices.length 36 }]; 37 obj_mesh.parentId=mesh.parent?mesh.parent.id:null; 38 } 39 else 40 { 41 obj_mesh.positions=[]; 42 obj_mesh.normals=[]; 43 obj_mesh.uvs=[]; 44 obj_mesh.indices=[]; 45 obj_mesh.subMeshes=[{ 46 'materialIndex': 0, 47 'verticesStart': 0, 48 'verticesCount': 0, 49 'indexStart': 0, 50 'indexCount': 0 51 }]; 52 obj_mesh.parentId=null; 53 } 54 obj_scene.meshes.push(obj_mesh); 55 }
c、向模型中添加骨骼並向骨骼中添加骨頭:
1 newland.AddSK2Model=function(obj_scene,skname) 2 { 3 var obj_sk={id:obj_scene.skeletons.length,name:skname,bones:[],ranges:[] 4 ,needInitialSkinMatrix:false} 5 obj_scene.skeletons.push(obj_sk); 6 } 7 newland.AddBone2SK=function(obj_scene,i,bone) 8 { 9 obj_scene.skeletons[i].bones.push(bone)//也許應該用splice?? 10 }
d、用上述方法初始化網格與模型:(在html文件裏)
1 //在這裏設置一個初始的默認網格, 2 mesh_origin=new BABYLON.MeshBuilder.CreateSphere("mesh_origin",{diameter:8,diameterY:64,segments:16},scene); 3 mesh_origin.material=mat_frame; 4 var vb=mesh_origin.geometry._vertexBuffers; 5 var data_pos=vb.position._buffer._data; 6 var len_pos=data_pos.length; 7 mesh_origin.matricesIndices=newland.repeatArr([0],len_pos/3);//頂點的骨頭索引 8 mesh_origin.matricesWeights=newland.repeatArr([1,0,0,0],len_pos/3);//頂點的骨頭權重 9 mesh_origin.skeletonId=0; 10 obj_scene=newland.CreateObjScene(); 11 newland.AddMesh2Model(obj_scene,mesh_origin,"mesh_origin2"); 12 newland.AddSK2Model(obj_scene,"sk_test1");//向模型中添加骨骼 13 var bone={ 14 'animation':{ 15 dataType:3, 16 framePerSecond:num_fps, 17 keys:[], 18 loopBehavior:1, 19 name:'_bone'+0+'Animation', 20 property:'_matrix' 21 }, 22 'index':0, 23 'matrix':BABYLON.Matrix.Identity().toArray(), 24 'name':'_bone'+0, 25 'parentBoneIndex':-1 26 }; 27 //bone. 28 newland.ExtendKeys(bone,sum_frame);//初始擴展根骨骼的關鍵幀,認爲根骨骼是一直保持不變的 29 newland.AddBone2SK(obj_scene,0,bone);// 向骨骼中添加骨頭 30 arr_bone=obj_scene.skeletons[0].bones; 31 BABYLON.Animation.AllowMatricesInterpolation = true;//動畫矩陣插值
這裏創建了骨頭0做爲全部骨骼最底層的根骨骼,它保持不變,不參加後面的各項設置。
e、添加一個編輯區(一塊骨頭):(在ClickButton.js文件中)
1 function addBone()//向列表裏添加一塊骨骼 2 { 3 var container=document.getElementById("div_flexcontainer"); 4 container.appendChild(document.querySelectorAll("#div_hiden .div_flexible")[0].cloneNode(true)); 5 var divs=container.querySelectorAll(".div_flexible"); 6 var len=divs.length; 7 divs[len-1].number=len;//這個屬性並不能準確的使用 8 divs[len-1].querySelectorAll(".str_flexlen")[0].innerHTML=len+""; 9 var bone={ 10 'animation':{ 11 dataType:3, 12 framePerSecond:num_fps, 13 keys:[], 14 loopBehavior:1, 15 name:'_bone'+len+'Animation', 16 property:'_matrix' 17 }, 18 'index':len, 19 'matrix':BABYLON.Matrix.Identity().toArray(), 20 'name':'_bone'+len, 21 'parentBoneIndex':0 22 } 23 newland.AddBone2SK(obj_scene,0,bone); 24 }
四、導入其餘模型
做爲模型編輯工具不可能只處理初始模型,使用ImportMesh方法導入其餘的Babylon.js模型代替初始模型:
1 /* 2 * ImportMesh("","../ASSETS/SCENE/","10.babylon") 3 * ImportMesh("","../ASSETS/SCENE/","SpaceCraft.babylon") 4 * */ 5 function ImportMesh(objname,filepath,filename) 6 { 7 8 BABYLON.SceneLoader.ImportMesh(objname, filepath, filename, scene 9 , function (newMeshes, particleSystems, skeletons) 10 {//載入完成的回調函數 11 newland.ClearMeshinModel(obj_scene); 12 if(mesh_origin&&mesh_origin.dispose) 13 { 14 mesh_origin.dispose(); 15 } 16 mesh_origin=newMeshes[0]; 17 mesh_origin.material=mat_frame; 18 //mesh_origin.layerMask=2; 19 var vb=mesh_origin.geometry._vertexBuffers; 20 var data_pos=vb.position._buffer._data; 21 var len_pos=data_pos.length; 22 mesh_origin.matricesIndices=newland.repeatArr([0],len_pos/3); 23 mesh_origin.matricesWeights=newland.repeatArr([1,0,0,0],len_pos/3); 24 mesh_origin.skeletonId=0; 25 newland.AddMesh2Model(obj_scene,mesh_origin,"mesh_origin2"); 26 } 27 ); 28 }
五、骨骼劃分:
a、點擊刷新按鈕時根據編輯區的輸入創建平面:
1 function ClearAllClip()//只清理全部的斜面,不處理突出的頂點 2 { 3 var len=arr_plane.length; 4 for(var i=0;i<len;i++) 5 { 6 var plane=arr_plane[i]; 7 plane.cylinder.dispose(); 8 plane.mesh.dispose(); 9 plane.lines_normal.dispose(); 10 11 plane=null; 12 } 13 arr_plane=[]; 14 } 15 16 var arr_plane=[];//保存全部的平面 17 function ShowClip()//先預留,不然之後要用時添加起來很麻煩 18 { 19 20 } 21 function ShowClip2()//再點擊刷新時根據斜面計算當前骨骼區域 22 { 23 var evt=evt||window.event||arguments[0]; 24 cancelPropagation(evt); 25 var obj=evt.currentTarget?evt.currentTarget:evt.srcElement;//obj是刷新按鈕 26 ClearAllClip();//先清空全部可能存在的平面 27 var divs=obj.parentNode.parentNode.querySelectorAll(".div_flexbottom")[0].querySelectorAll(".div_flexcell"); 28 var str_number=obj.parentNode.parentNode.querySelectorAll("span")[0].innerHTML//.getAttribute("number");//這是骨骼索引編號 29 var len=6; 30 for(var i=0;i<len;i++)//遍歷每一個斜面設置,繪製出相應斜面 31 { 32 var div=divs[i]; 33 var inputs=div.querySelectorAll("input"); 34 var len2=4; 35 var flag=0; 36 var arr=[]; 37 for(var j=0;j<len2;j++)//判斷這個斜面是否正常設置 38 { 39 if(isNaN(parseFloat(inputs[j].value)))//若是這個文本框沒有內容或者內容不是數字 40 { 41 flag=1; 42 break; 43 } 44 else 45 { 46 arr.push(parseFloat(inputs[j].value)); 47 } 48 } 49 if(flag==0)//若是能夠構成平面 50 { 51 var plane=new BABYLON.Plane(arr[0], arr[1], arr[2], arr[3]); 52 var div_comment=div.querySelectorAll(".div_comment")[0]; 53 if(!div_comment)//若是之前沒有這個註釋內容 54 { 55 div_comment=document.createElement("div");//創建一個隱形元素把設置持久化 56 div_comment.style.display="none"; 57 div_comment.className="div_comment"; 58 div.appendChild(div_comment); 59 } 60 div_comment.innerHTML=JSON.stringify(arr); 61 62 plane.normalize();//必須先把平面標準化,不然生成的平面網格不許確(會參考向量長度生成) 63 var mesh_plane=new BABYLON.MeshBuilder.CreatePlane("mesh_plane"+i 64 ,{sourcePlane:plane,sideOrientation:BABYLON.Mesh.DOUBLESIDE,size:50},scene); 65 //sourcePlane傾斜時sourcePlane有Bug!!!!?? 66 mesh_plane.material=mat_alpha_yellow;//由plane生成的mesh沒有rotation?? 67 var pos1=mesh_plane.position.clone(); 68 var vec_nomal=plane.normal.clone().normalize(); 69 var pos2=pos1.add(vec_nomal); 70 var lines=[[pos1,pos2]]; 71 var lines_normal=new BABYLON.MeshBuilder.CreateLineSystem("lines_normal"+i,{lines:lines,updatable:false},scene); 72 lines_normal.color=new BABYLON.Color3(1, 0, 0); 73 var cylinder = BABYLON.MeshBuilder.CreateCylinder("cylinder"+i,{height:1,diameterTop:0,diameterBottom:0.2 } ,scene); 74 cylinder.parent=mesh_plane; 75 cylinder.rotation.x-=Math.PI/2; 76 cylinder.position.z-=1.5; 77 cylinder.material=mat_red; 78 79 plane.mesh=mesh_plane; 80 plane.lines_normal=lines_normal; 81 plane.cylinder=cylinder; 82 arr_plane.push(plane); 83 } 84 else 85 { 86 var div_comment=div.querySelectorAll("div_comment")[0];//若是這個平面設置不成立,但又有記錄的數據,則清空記錄的數據 87 if(div_comment) 88 { 89 delete_div(div_comment); 90 } 91 } 92 } 93 requestAnimFrame(function(){FindVertex(str_number);}); 94 //FindVertex(str_number);//尋找屬於這塊骨骼的頂點 95 }
第28行使用編輯區左上角的數字區分當前編輯的是哪一塊骨頭。
接下來遍歷編輯區的每一行,若是這一行的輸入符合構成平面的要求,則創建一個平面對象,而後把這一行輸入的內容以隱形標籤的形式保存在dom文檔中。
接下來對平面進行標準化操做,在用標準化平面創建平面網格(63行),這裏要注意「平面」是Babylon.js中的一類數學對象,並不實際顯示,平面網格纔是實際顯示的對象。所謂標準化指保持方向不變讓平面的方向向量模爲1。
再接下來在平面網格的中央創建一條線段和一個圓錐體網格表明法向量。
最後告知瀏覽器在下一幀渲染時執行頂點查找計算,發現直接執行FindVertex方法程序會出錯,限於時間並未深刻研究緣由。
b、使用平面對象選擇頂點:
1 var lines_inpicked=null; 2 function FindVertex(str_number)//突出顯示骨骼範圍內的全部頂點 3 { 4 if (divFps) { 5 // Fps 6 divFps.innerHTML = "0fps"; 7 } 8 if(!mesh_origin||!mesh_origin.dispose) 9 { 10 console.log("還沒有加載模型"); 11 return; 12 } 13 if(lines_inpicked&&lines_inpicked.dispose) 14 { 15 lines_inpicked.dispose(); 16 } 17 var len=arr_plane.length; 18 if(len>0)//若是有平面,則開始遍歷模型頂點 19 { 20 var mesh=mesh_origin; 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 lines=[]; 27 var matricesIndices=mesh_origin.matricesIndices; 28 var matricesWeights=mesh_origin.matricesWeights; 29 30 for(var i=0;i<len_pos;i+=3)//對於每一個頂點 31 { 32 console.log(i/3+1+"/"+len_pos/3);//顯示當前操做到第幾個頂點 33 if(matricesIndices[i/3]==parseInt(str_number))//要清空舊的設定 34 { 35 matricesIndices[i/3]=0; 36 } 37 var pos=new BABYLON.Vector3(data_pos[i],data_pos[i+1],data_pos[i+2]); 38 var flag=0; 39 for(var j=0;j<len;j++)//對於每個切分平面 40 { 41 var num=arr_plane[j].signedDistanceTo(pos); 42 if(num<0) 43 { 44 flag=1; 45 break; 46 } 47 } 48 if(flag==0) 49 { 50 var index_vertex=i/3; 51 var vec=pos; 52 matricesIndices[index_vertex]=parseInt(str_number);//修改這個頂點的骨骼綁定 53 //下面進行突出顯示,遍歷索引? 54 for(var j=0;j<len_index;j+=3) 55 { 56 if(index_vertex==data_index[j])//三角形的第一個頂點 57 { 58 var num2=data_index[j+1]*3; 59 var num3=data_index[j+2]*3; 60 var vec2=new BABYLON.Vector3(data_pos[num2],data_pos[num2+1],data_pos[num2+2]); 61 var vec3=new BABYLON.Vector3(data_pos[num3],data_pos[num3+1],data_pos[num3+2]); 62 lines.push([vec,vec2]); 63 lines.push([vec,vec3]); 64 } 65 else if(index_vertex==data_index[j+1])//三角形的第一個頂點 66 { 67 var num2=data_index[j]*3; 68 var num3=data_index[j+2]*3; 69 var vec2=new BABYLON.Vector3(data_pos[num2],data_pos[num2+1],data_pos[num2+2]); 70 var vec3=new BABYLON.Vector3(data_pos[num3],data_pos[num3+1],data_pos[num3+2]); 71 lines.push([vec,vec2]); 72 lines.push([vec,vec3]); 73 } 74 else if(index_vertex==data_index[j+2])//三角形的第一個頂點 75 { 76 var num2=data_index[j]*3; 77 var num3=data_index[j+1]*3; 78 var vec2=new BABYLON.Vector3(data_pos[num2],data_pos[num2+1],data_pos[num2+2]); 79 var vec3=new BABYLON.Vector3(data_pos[num3],data_pos[num3+1],data_pos[num3+2]); 80 lines.push([vec,vec2]); 81 lines.push([vec,vec3]); 82 } 83 } 84 } 85 } 86 lines_inpicked=new BABYLON.MeshBuilder.CreateLineSystem("lines_inpicked",{lines:lines,updatable:false},scene); 87 lines_inpicked.color=new BABYLON.Color3(0, 1, 0); 88 } 89 else 90 { 91 console.log("沒有設置符合規則的斜面"); 92 return; 93 } 94 }
對於每個頂點,首先清空它當前的骨頭索引(35行,將來考慮設置多骨頭)。
接着在41行用signedDistanceTo方法得到這個頂點到平面的距離,若是頂點在平面的法線方向(正向)則距離爲正,反之爲負。
若是對於全部切分平面(最多六個)這個頂點都在正向,則將這個頂點的骨頭索引設爲當前編輯的骨頭。而且高亮顯示這個頂點所在的邊。(高亮顯示頂點的方法能夠參考https://www.cnblogs.com/ljzc002/p/9353101.html)
六、關鍵幀腳本解析:
爲了簡化關鍵幀矩陣的設置,我創建了一套簡單的關鍵幀腳本規則,它的解析方法以下:(代碼位於ComputeMatrix.js文件中)
1 function InsertKey()//根據關鍵幀對mesh的骨骼的關鍵幀矩陣進行修改 2 { 3 var div_open=document.querySelectorAll("#div_open")[0];//彈出的窗口對象 4 var obj={}; 5 obj.str_indexp=parseInt(div_open.querySelectorAll(".str_indexp")[0].value); 6 //obj.str_fps=div_open.querySelectorAll(".str_fps")[0].value; 7 obj.str_posjx=parseInt(div_open.querySelectorAll(".str_posjx")[0].value); 8 obj.str_posjy=parseInt(div_open.querySelectorAll(".str_posjy")[0].value); 9 obj.str_posjz=parseInt(div_open.querySelectorAll(".str_posjz")[0].value); 10 obj.text_key=div_open.querySelectorAll(".text_key")[0].value; 11 var str_key=obj.text_key; 12 str_key.replace("?","");//替換掉換行符,在Chrome裏換行符是回車的豎折箭頭,但這裏被顯示成了? 13 str_key.replace("\r",""); 14 str_key.replace("\n",""); 15 var arr_key=str_key.split("#"); 16 var len=arr_key.length; 17 //計算這一塊骨骼的關鍵幀 18 var bone=arr_bone[flex_current.querySelectorAll("span")[0].innerHTML]//.getAttribute("number")]; 19 //var inputs=document.querySelectorAll("#div_open input"); 20 //bone.animation.framePerSecond=parseInt(inputs[1].value); 21 bone.parentBoneIndex=obj.str_indexp; 22 bone.animation.keys=[];//每次點擊計算時都會重寫這塊骨頭的全部初始關鍵幀-》擴展關鍵幀要放在後面的環節!!!! 23 var div_comment=flex_current.querySelectorAll(".div_comment0")[0];//註釋信息從伸縮對象中提取 24 if(!div_comment)//將對於關鍵幀的各項設置保存在一個隱形標籤裏,這樣下次打開對話框就不須要從新輸入了 25 { 26 div_comment=document.createElement("div"); 27 div_comment.style.display="none"; 28 div_comment.className="div_comment0"; 29 flex_current.appendChild(div_comment); 30 } 31 div_comment.innerHTML=JSON.stringify(obj); 32 try 33 { 34 var pos_gj=new BABYLON.Vector3(obj.str_posjx,obj.str_posjy,obj.str_posjz);//關節點的座標 35 bone.pos_gj=pos_gj;//記錄這塊骨頭和父骨頭之間的關節點的全局位置 36 for(var i=0;i<len;i++)//對於每個關鍵幀 37 { 38 var key=arr_key[i];//單條關鍵幀的腳本代碼 39 var arr=key.split("@"); 40 var num_frame=parseInt(arr[0]);//當前幀數 41 var script_frame=arr[1]; 42 var matrix=eval(script_frame);//根據腳本計算出矩陣對象 43 44 //var count=bone.animation.keys.length; 45 //var matrix2=LoadParent4ThisKey(bone,count,true); 46 47 //var vec_temp2=BABYLON.Vector3.TransformCoordinates(pos_gj,matrix2) 48 // .subtract(BABYLON.Vector3.TransformCoordinates(pos_gj,matrix.multiply(matrix2))); 49 //bone.animation.keys.push({frame:num_frame,values:matrix.multiply(BABYLON.Matrix.Translation(vec_temp2.x,vec_temp2.y,vec_temp2.z)).toArray() 50 //});//推入每一條關鍵幀 51 bone.animation.keys.push({frame:num_frame,values:matrix.toArray()}) 52 53 } 54 flex_current.querySelectorAll(".checkbone")[0].checked=true; 55 } 56 catch(e) 57 { 58 console.error(e); 59 } 60 finally 61 {//操做完畢後關閉對話框 62 delete_div('div_open'); 63 delete_div("div_mask"); 64 } 65 66 }
七、預覽模型
完成前面的骨頭編輯後點擊預覽模型,程序開始進入最關鍵的矩陣計算流程,首先是預覽和導出模型的代碼:
1 var mesh_test=null;//必須先期聲明一下,不然在ImportMesh的回調中會報錯!!!! 2 function ExportMesh(obj_scene,flag)//這個工程專用的導出方法 3 { 4 5 if(flag==1)//點擊導出按鈕,默認此時已經完成了擴展關鍵幀和矩陣傳遞的計算 6 { 7 var str_data=JSON.stringify(obj_scene); 8 DownloadText(MakeDateStr()+"testscene",str_data,".babylon"); 9 } 10 else if(flag==0)//點擊現場演示按鈕 11 { 12 var str_data=""; 13 //var bones=obj_scene.skeletons[0].bones; 14 HandleBones();//對骨骼動畫的關鍵幀進行處理 15 16 str_data=JSON.stringify(obj_scene); 17 //在現場演示環節裏添加擴展關鍵幀的計算?? 18 BABYLON.SceneLoader.ImportMesh("", "", "data:"+str_data, scene 19 , function (newMeshes, particleSystems, skeletons) {//載入完成的回調函數 20 try{ 21 if(mesh_test) 22 { 23 mesh_test.dispose(); 24 } 25 mesh_test=newMeshes[0]; 26 mesh_test.position.x=50; 27 mesh_test.material=mat_frame; 28 //var totalFrame=skeletons[0]._scene._activeSkeletons.data.length; 29 skeleton=skeletons[0]; 30 scene.beginAnimation(skeleton, 0, sum_frame, true, 0.5);//啓動骨骼動畫 31 } 32 catch(e)//在這裏攔截異常,不然異常進入Babylon.js會致使瀏覽器卡頓!! 33 { 34 console.log(e); 35 } 36 37 }); 38 } 39 }
能夠看到,只有現場演示按鈕的響應裏具有HandleBones方法,因此在完成現場演示以後才能夠點擊導出模型。
另外一個須要注意的問題是在調試模式下,一旦ImportMesh的回調函數中出現未攔截的異常,瀏覽器將嘗試打開Babylon.js文件進行調試,並將整個模型文件的文本做爲異常信息輸出,這會消耗極大的CPU資源,形成瀏覽器卡頓甚至崩潰,因此嘗試catch一下防止這類問題。
八、關鍵幀擴展
在前面的腳本里咱們只設置了9個間隔30幀的關鍵幀,雖然Babylon.js也支持自動對動畫關鍵幀矩陣進行插值,但爲了不線性插值形成的骨骼縮放和錯位(https://www.cnblogs.com/ljzc002/p/8927221.html展現了不擴展關鍵幀的缺點),在這裏主動將9個初始關鍵幀擴展爲241個擴展關鍵幀。
1 function HandleBones()//對每一個骨頭擴展關鍵幀,並創建矩陣傳遞 2 { 3 var len=arr_bone.length; 4 var total=len*sum_frame; 5 console.log("開始擴展非根骨頭的關鍵幀"); 6 for(var i=1;i<len;i++)//從新擴展根骨骼以外的全部骨頭 7 { 8 var bone=arr_bone[i]; 9 newland.ExtendKeys(bone,sum_frame); 10 console.log(i+"/"+(len-1)); 11 }
1 newland.ExtendKeys=function(bone,sum_frame) 2 { 3 var keys=bone.animation.keys; 4 var keys2=[]; 5 if(keys.length==0)//若是是根骨骼一類沒有初始關鍵幀的骨骼 6 { 7 for(var i=0;i<sum_frame;i++) 8 { 9 keys2.push({frame:i,values:BABYLON.Matrix.Identity().toArray()}); 10 } 11 } 12 else 13 { 14 var count_frame=0;//當前擴展到第幾幀 15 for(var i=0;i<keys.length;i++)//對於每個初始關鍵幀 16 { 17 var key1=keys[i]; 18 if(i<(keys.length-1)) 19 { 20 var key2=keys[i+1]; 21 var frame_between=key2.frame-key1.frame; 22 var j=0; 23 var m1=BABYLON.Matrix.FromArray(key1.values); 24 var m2=BABYLON.Matrix.FromArray(key2.values); 25 while(count_frame<=key2.frame) 26 { 27 var rate=j/frame_between; 28 var m_lerp=BABYLON.Matrix.Lerp(m1,m2,rate); 29 newland.NormalizeMatrix(m_lerp); 30 keys2.push({frame:count_frame,values:m_lerp.toArray()}); 31 count_frame++; 32 j++; 33 } 34 } 35 else 36 { 37 while(count_frame<sum_frame) 38 { 39 keys2.push({frame:count_frame,values:key1.values});//這values應該是一個對象,但只是導出JSON的話彷佛並不用克隆新對象 40 count_frame++; 41 } 42 } 43 } 44 } 45 bone.animation.keys=keys2; 46 return keys2; 47 }
ExtendKeys方法遍歷骨骼中已經設定的初始關鍵幀,若是沒有設置初始關鍵幀,則使用不含任何變化的單位矩陣填滿241個擴展關鍵幀(好比根骨骼),若是設置了初始關鍵幀,則先用線性插值獲取擴展關鍵幀,並對擴展關鍵幀進行標準化操做。
矩陣標準化避免了線性插值致使的骨頭尺寸縮放,但代價是讓使用者沒法設置尺寸縮放的骨骼動畫,考慮另加一個維護尺寸變化的變量,對標準化以後的矩陣再次縮放。
另一個須要注意的地方是,Babylon.js骨頭對象裏以一維數組的形式保存矩陣(matrix.toArray()),而實際進行矩陣變換時則要用矩陣類對象進行操做(BABYLON.Matrix.FromArray(arr);)。
通過上述計算,全部的骨頭都被擴展爲241了個關鍵幀。
九、繼承父骨骼的矩陣變換:
由於大學數學學的很差,這一段的算法並無足夠的理論基礎,主要使用高中數學知識經過推理和反覆試驗得出:(HandleBones的下半部分)
1 var joints=[]; 2 //var joint0=arr_bone[1].pos_gj; 3 console.log("開始調整非根骨頭的關鍵幀") 4 for(var i=1;i<len;i++)//對於除根骨頭以外的每一塊骨頭計算傳遞矩陣 5 {//回溯每塊骨頭的全部父骨頭 6 var bone=arr_bone[i]; 7 joints=[];//提取全部的關節距離 8 var sum_j=new BABYLON.Vector3(0,0,0); 9 var bone_now=bone; 10 while(true) 11 {//獲取這塊骨頭和它全部父骨頭的關節信息 12 var parent=arr_bone[bone_now.parentBoneIndex];//取父骨骼 13 14 if(parent.index!=0)//若是尚未回溯到根骨骼 15 { 16 joints.unshift({pos_gj:bone_now.pos_gj.clone().subtract(parent.pos_gj),index:parent.index}); 17 sum_j=sum_j.add(bone_now.pos_gj.clone().subtract(parent.pos_gj)); 18 bone_now=parent; 19 } 20 else 21 { 22 joints.unshift({pos_gj:bone_now.pos_gj,index:0}); 23 sum_j=sum_j.add(bone_now.pos_gj); 24 break; 25 } 26 } 27 var keys=bone.animation.keys; 28 var keys2=[];//數組形式表示的矩陣的數組 29 bone.keys2=keys2; 30 bone.vec_adjusts=[]; 31 bone.temp1=[];//父。multiply子 32 bone.temp1b=[];//子。multiply父 33 bone.sum_j=sum_j; 34 bone.temp3=[]; 35 bone.joint=joints[joints.length-1].pos_gj.clone();//這塊骨頭的上一塊骨頭的形態 36 for(var j=0;j<sum_frame;j++)//對於這塊骨頭的每個幀 37 { 38 var matrix=ms.fa(keys[j].values);//這一幀的局部變換矩陣 39 var parent=arr_bone[bone.parentBoneIndex]; 40 if(bone.parentBoneIndex==0)//第一層骨頭 41 { 42 bone.temp1.push(matrix); 43 bone.temp1b.push(matrix); 44 bone.vec_adjusts.push(bone.pos_gj.clone().subtract(vs.tr(sum_j.clone(),matrix)));//這個是全局座標系中的位移 45 bone.temp3.push(bone.pos_gj.clone()); 46 } 47 else//第二層及以上骨頭 48 { 49 50 var matrix2=parent.temp1[j].clone().multiply(matrix); 51 bone.temp1.push(matrix2); 52 var matrix2b=(matrix.clone()).multiply(parent.temp1b[j].clone()); 53 bone.temp1b.push(matrix2b); 54 var vec=vs.tr(bone.joint.clone(),parent.temp1b[j].clone()); 55 bone.temp3.push(parent.temp3[j].clone().add(vec)); 56 bone.vec_adjusts.push((parent.temp3[j].clone().add(vec).subtract(vs.tr(bone.sum_j,matrix2)))); 57 } 58 var vec_adjust=bone.vec_adjusts[j].clone(); 59 if(bone.parentBoneIndex!=0) 60 { 61 vec_adjust=vs.tr(vec_adjust.subtract(parent.vec_adjusts[j]),parent.temp1b[j].clone().invert()); 62 } 63 var matrix_adjusted=matrix.multiply(ms.tr(vec_adjust.x,vec_adjust.y,vec_adjust.z))//變換後的這塊骨頭這一幀的矩陣 64 keys2.push({frame:j,values:matrix_adjusted.toArray()}); 65 //var vec_adjust1=joint0.clone(),vec_adjust2=vs.tr(sum_j.clone(),matrix);//默認至少有一塊一層骨頭 66 //對於每一層父元素 67 //var len2=joints.length;//關節的數量比骨頭的數量少一 68 /*for(var k=1;k<len2;k++)//對於每個上游關節 69 {//這種寫法是強制進行全部計算 70 var vec1=joints[k].pos_gj; 71 for(var l=1;l<=k;l++) 72 { 73 vec1=vs.tr(vec1,ms.fa(arr_bone[joints[l].index].animation.keys[j].values)); 74 } 75 vec_adjust1=vec_adjust1.add(vec1); 76 77 vs.tr(vec_adjust2,ms.fa(arr_bone[joints[k].index].animation.keys[j].values))//取這個關節所在的骨頭在這一幀的變換矩陣 78 79 }*/ 80 /*for(var k=0;k<len2;k++)//對於每個上游關節 81 { 82 83 } 84 var vec_adjust=vec_adjust1.subtract(vec_adjust2); 85 var matrix_adjusted=matrix.multiply(ms.tr(vec_adjust.x,vec_adjust.y,vec_adjust.z))//變換後的這塊骨頭這一幀的矩陣 86 keys2.push({frame:j,values:matrix_adjusted.toArray()}); 87 bone.vec_adjusts.push(vec_adjust);//以此優化之*/ 88 } 89 console.log(i+"/"+(len-1)); 90 } 91 for(var i=1;i<len;i++)//對於除根骨頭以外的每一塊骨頭用剛纔計算出的keys2替換keys 92 { 93 var bone=arr_bone[i]; 94 //var temp=bone.animation.keys; 95 bone.animation.keys=bone.keys2;//交換 96 //bone.keys2=temp; 97 delete bone.keys2;//節省文件大小 98 delete bone.temp1; 99 delete bone.vec_adjusts; 100 } 101 }
a、下面用幾幅圖介紹上述代碼中的算法:
假設全部的骨骼動畫都是繞x軸旋轉,ABC表示關節點,abc表示轉過的角度,j0、j一、j2表示兩個關節點之間的向量,j0·a表示對向量j0施加轉過角度a的矩陣變換。
圖一爲只有一塊非根骨頭,且骨頭的關節位置不在原點的狀況,咱們把不動的根骨頭叫作「骨頭0」繞A點旋轉的部分叫作「骨頭1」。咱們但願骨頭1繞A點旋轉a角度到達圖中所示實線位置,但若是咱們只把骨頭1的關鍵幀矩陣設爲a,則在實際執行動畫時骨骼1會以原點爲軸旋轉a處於圖中虛線位置,咱們將從虛線平移到實線的向量定義爲「vec_adjust」(修正向量)。對骨頭的每一個幀的矩陣施加這一修正向量便可將關鍵幀調整到目的位置。
在圖一的狀況下vec_adjust=j0-j0·a。
圖二中有兩塊非根骨頭(攝像條件有限,湊活看吧),修正向量爲j0+j1·a-(j0+j1)·a·b。
圖三中有三塊非根骨頭,爲了簡化思考讓A點與原點重合,修正向量爲j1·a+j2·a·b-(j1+j2)·a·b·c。
綜合以上三種狀況猜想修正向量的計算規律爲:
vec_adjust=j0+j1·a+j2·a·b-(j0+j1+j2)·a·b·c
後通過實際測試發現規律應該是:
vec_adjust=j0+j1·a+j2·b·a-(j0+j1+j2)·a·b·c
但並不知道原理是什麼。
觀察可知子骨頭的修正向量裏包含父骨頭的修正向量的組成部分,爲了節省計算消耗,對於骨頭2設j0+j1·a=temp三、a·b爲temp一、b·a爲temp1b、j0+j1對應關節向量的和sum_j。
因而對於這塊骨頭的241個幀,算出每一個幀的vec_adjust。
b、接下來對計算出的修正向量再進行兩步處理
首先,子骨骼會繼承父骨骼在同一幀的矩陣變換,這其中也包含了父骨骼的修正向量,因此子骨骼的修正向量應該改成子骨骼修正向量與父骨骼修正向量的差值。(這裏也許能夠對計算進行化簡)
另外,咱們剛纔計算出的修正向量是世界座標系中的,須要將它轉化到骨頭的局部座標系中與骨頭一同接受父骨頭的影響。
上述代碼的第63行完成了這兩步操做。
c、對關鍵幀矩陣施加修正向量表明的矩陣變化,而且清理掉bone對象中的多餘屬性,以減少生成的模型文件的尺寸。
如此就完成了骨骼模型的矩陣計算工做。
十、保存和載入骨骼設置
直接使用控制檯複製div_flexcontainer標籤中的內容粘貼到一個複製的html頁中便可。
頁面加載時會執行reInit方法讀取以前的設置:
1 function reInit() 2 { 3 var flexs=document.querySelectorAll("#div_flexcontainer")[0].querySelectorAll(".div_flexible"); 4 var len=flexs.length; 5 for(var i=0;i<len;i++)//對於加載的每個flex對象 6 { 7 var flex=flexs[i]; 8 var bone={ 9 'animation':{ 10 dataType:3, 11 framePerSecond:num_fps, 12 keys:[], 13 loopBehavior:1, 14 name:'_bone'+(i+1)+'Animation', 15 property:'_matrix' 16 }, 17 'index':(i+1), 18 'matrix':BABYLON.Matrix.Identity().toArray(), 19 'name':'_bone'+(i+1), 20 'parentBoneIndex':0 21 } 22 newland.AddBone2SK(obj_scene,0,bone); 23 24 var divs=flex.querySelectorAll(".div_flexcell");//根據可能存在的初始值初始化文本框,可是還須要手動點擊每一個骨骼的刷新按鈕 25 var len2=divs.length; 26 for(var j=0;j<len2;j++)//初始化每一個斜面的輸入值 27 { 28 var div_comment=divs[j].querySelectorAll(".div_comment")[0]; 29 if(div_comment)//若是這個平面有記錄的數據 30 { 31 var arr=JSON.parse(div_comment.innerHTML); 32 var inputs=divs[j].querySelectorAll("input"); 33 inputs[0].value=arr[0]; 34 inputs[1].value=arr[1]; 35 inputs[2].value=arr[2]; 36 inputs[3].value=arr[3]; 37 } 38 } 39 40 var div_bottom=flex.querySelectorAll(".div_flexbottom")[0]; 41 if(div_bottom.style.display=="block") 42 { 43 flex_current=flex; 44 } 45 } 46 }
3、總結
目前的骨骼編輯器功能很是原始,不支持多骨骼綁定、不支持縮放類骨骼動畫,算法也未通過嚴格測試,可能存在各類問題,歡迎各位大佬幫忙測試指出問題。