基于Babylon.js编写简单的骨骼动画生成器
使用骨骼动画技术可以将网格的顶点分配给若干骨头,通过给骨头设定关键帧和父子关系,可以赋予网格高度动态并具有传递性的变形 效果。这里结合之前的相关研究在网页端使用javascript实现了一个简单的骨骼动画编辑和模型生成工具。
一、显示效果:
1、访问https://ljzc002.github.io/bones/html/cstestspacecraft2.html查看测试页面:
屏幕右侧的babylon.js场景中是一个初始网格。
2、在chrome浏览器控制台输入“importmesh("","../assets/scene/","spacecraft.babylon")”,载入之前编写的一个宇宙飞船模型,关于这个模型的编写方式可以参考https://www.cnblogs.com/ljzc002/p/9473438.html
3、点击“新增骨骼”按钮,会在左侧建立一个可折叠的骨头编辑区(标签的类名是div_flexible),一个编辑区分为六行,每行包括四个文本框。
4、在第一行的四个文本框中输入-1、0、0、0,点击这个编辑区的刷新按钮,将在场景中建立一个朝向(-1,0,0)方向距原点距离为0的平面,所有包含平面正面(或平面上)顶点的线会被标为绿色(“正面”可以理解为从平面出发,沿着平面法线方向移动可以到达这个顶点,数学上可以说“这个顶点到平面的距离为正”)
当顶点的数量较多时,上述计算会花费一定时间,控制台里会打印出当前的查找进度。
在编辑区的第二行输入0、1、0、-3(表示沿法线的反方向到原点的距离为3)会建立另一个平面,同时处于两个平面正面的顶点会被选中,最多可以建立6个这样的区分平面。
5、选定这些顶点作为一号骨头后,点击“编辑关键帧”按钮将打开一号骨头的关键帧编辑对话框
其中父骨骼索引设为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;
点击“写入初始关键帧”,则关键帧设置被保存,同时编辑区上的复选框会被选中。
6、再给宇宙飞船的翅膀设置骨骼:
设置关键帧
因为是取z值小于等于-6的顶点组成骨头2,所以将关节点位置设为(0,0,-6),当然,也可以把关节点设在其他位置,如过这样做翅膀的运动方式将有所不同。
对称的设置右侧的翅膀,生成骨头3。
7、设置完成后,点击“预览模型”按钮,将在场景的x方向显示骨骼动画效果:
这里体现出当前工具的一个缺点:尚未允许同一顶点绑定多块骨头,对于重复选取的顶点,后设置的骨头会覆盖之前的设置。
因为博客园对图片大小有限制,只能截取骨骼动画的一部分。
点击“导出模型”则可以文本方式导出上述含有骨骼动画的模型。
8、父骨头对子骨头的影响:
可以访问https://ljzc002.github.io/bones/html/cstest2.html页面,刷新并编辑三块骨头的关键帧可以看到骨骼动画的传递效果。
二、代码实现
1、工程结构:
其中clickbutton.js里是除矩阵计算外所有和按钮响应相关的代码,computematrix.js里是所有和矩阵计算有关的代码,flex.js没用。
2、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 }
3、模型对象的初始化:
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 }
4、导入其他模型
作为模型编辑工具不可能只处理初始模型,使用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 }
5、骨骼划分:
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)
6、关键帧脚本解析:
为了简化关键帧矩阵的设置,我建立了一套简单的关键帧脚本规则,它的解析方法如下:(代码位于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 }
7、预览模型
完成前面的骨头编辑后点击预览模型,程序开始进入最关键的矩阵计算流程,首先是预览和导出模型的代码:
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一下防止这类问题。
8、关键帧扩展
在前面的脚本里我们只设置了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,v
相关文章:
-
-
MongoDB简介 MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。... [阅读全文]
-
执行npm run build,自动将打包后的项目压缩成zip文件,便于发送后端部署! ... [阅读全文]
-
索引的类型 PRIMARY KEY(主键索引): 用来标识唯一性,数据不可重复 ,主键列不能为NULL,并且每个表中有且只能有一个主键,还可以创建复... [阅读全文]
-
在刚结束的项目中对axios进行了实践(好不容易碰上一个不是jsonp的项目), 以下为在项目中对axios的封装,仅封装了post方法,因为项目中... [阅读全文]
-
【MYSQL笔记2】mysql字符集形式(utf8转gbk)
在使用中,可能我们在建表的时候要用到中文,因此这里简单备份下字符集格式;在mysql环境中输入 可以查看当前连接系统的参数 为了让mysql数据库能... [阅读全文] -
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
上一篇: SQL Server优化必备之任务调度
发表评论