ホームページ >ウェブフロントエンド >H5 チュートリアル >Chrome コンソールを使用して 3D モデルを編集する方法の実装 (コード)
この記事では、Chrome コンソールを使用して 3D モデルを編集する方法の実装 (コード) を紹介します。必要な方はぜひ参考にしてください。編集の核となるのは頂点位置とテクスチャカラーの編集であり、この編集方法は一般的なマウスのクリック&ドラッグによる編集方法と同様に、プログラミングによってモデルを直接編集する方法を見つけることです。メソッド間の関係は、フロントエンド プログラミングにおける「プログラマーが静的 Web ページを作成する」と「アーティストが Web ページをカットする」の関係に非常に似ています。
1. ツールの使用法:
1. https://ljzc002.github.io/SnowMiku/HTML/MakeRibbon.html にアクセスして、シーン ワールド座標系 (0 、 -10,0)、(0,0,0)、(0,10,0)、基準点として緑色のボールがあります。マウスで上下左右にドラッグすることでシーンを移動できます。 。
2. F12 キーを押して Chrome コンソールを開き、コンソールに「MakeRibbon(MakeRing(5,12),-10,2,11,"mesh_ribbon")」と入力して Enter キーを押します:
が描画されますシーン内 半径 5、サーフェス サブディビジョン 12、左端 -10、2 つのリング間の距離 2、合計 11 個のリングの円筒サーフェス。 ズームインして表示します: 3. ShowNormals(mesh_origin) と入力して、各頂点の法線方向を赤い線分で表示しますすべての法線を削除するには、DeleteMeshes([lines_normal]) と入力します。 DeleteMeshes([mesh_origin]) は円筒メッシュを削除します。
4. グリッド上の三角形にマウスを移動すると、三角形の頂点情報が表示されます。 ここで、「1:2-5」は、これが三角形の最初の頂点であることを意味します。この頂点は、インデックス 2 の円上にあります。リング (3 番目のリング) 上では、リング内のこの頂点のインデックスは 5 (つまり、6 番目の頂点) です。 5. PickPoints([[2,5],[3,5],[2,6]],mesh_origin) と入力してこれらの頂点を選択します 選択した頂点の影響を受けるすべての境界線が黄色でマークされます, この「選択」は見た目を変えるだけです。 6. TransVertex(mesh_origin,arr_ij,BABYLON.Matrix.Translation(0,0,-10)) と入力して、選択した頂点 10 を Z 軸の負の方向に移動します。移動された頂点は、ここで、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) と入力します: 変形したエフェクトが表示され、選択を続けることができます頂点を変換します 7. ExportMesh("1",mat_blue) と入力し、バビロン モデル ファイルを txt 形式でエクスポートします。ファイル名は「1.txt」です 8. エクスポートされた txt の名前を 9 に変更します。 babylon とプレイ Web サイトのディレクトリに入り、モデル プレビュー ページ https://ljzc002.github.io/SnowMiku/HTML/LoadBabylon.html にアクセスし、「ImportMesh("","../ASSETS/SCENE/","9」と入力します)コンソールの .babylon ") を使用して、エクスポートしたばかりのモデルをロードします。 2. プログラミングのアイデア: 1. まず、さまざまなテストに使用できる基本的なシーンを作成します。<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>建立一个条带网格生成器,能够输入参数生成起始条带,然后通过命令行选取并修改pathArray,最后导出生成的条带</title> <link href="../CSS/newland.css" rel="stylesheet"> <link href="../CSS/stat.css" rel="stylesheet"> <script src="../JS/MYLIB/Events.js"></script> <script src="../JS/MYLIB/FileText.js"></script> <script src="../JS/MYLIB/View.js"></script> <script src="../JS/LIB/babylon.32.all.maxs.js"></script><!--V3.2的稳定版本--> <script src="../JS/MYLIB/newland.js"></script> <script src="../JS/LIB/stat.js"></script> </head> <body> <div id="div_allbase"> <canvas id="renderCanvas"></canvas> <div id="fps" style="z-index: 301;"></div> </div> </body> <script> var VERSION=1.0,AUTHOR="lz_newland@163.com"; var machine,canvas,engine,scene,gl,MyGame={}; canvas = document.getElementById("renderCanvas"); engine = new BABYLON.Engine(canvas, true); gl=engine._gl;//可以结合使用原生OpenGL和Babylon.js; scene = new BABYLON.Scene(engine); var divFps = document.getElementById("fps"); window.onload=beforewebGL; function beforewebGL() { if(engine._webGLVersion==2.0)//输出ES版本 { console.log("ES3.0"); } else{ console.log("ES2.0"); } //MyGame=new Game(0,"first_pick","","http://127.0.0.1:8082/"); /*0-startWebGL * */ webGLStart(); } //从下面开始分成简单测试和对象框架两种架构 //简单测试 //全局对象 var light0//全局光源 ,camera0//主相机 ; //四种常用材质 var mat_frame = new BABYLON.StandardMaterial("mat_frame", scene); mat_frame.wireframe = true; var mat_red = new BABYLON.StandardMaterial("mat_red", scene); mat_red.diffuseColor = new BABYLON.Color3(1, 0, 0); mat_red.backFaceCulling=false; var mat_green = new BABYLON.StandardMaterial("mat_green", scene); mat_green.diffuseColor = new BABYLON.Color3(0, 1, 0); mat_green.backFaceCulling=false; var mat_blue = new BABYLON.StandardMaterial("mat_blue", scene); mat_blue.diffuseColor = new BABYLON.Color3(0, 0, 1); mat_blue.backFaceCulling=false; var mesh_origin; var advancedTexture=BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("ui1");//全屏GUI function webGLStart() { window.addEventListener("resize", function () {//自动调整视口尺寸 engine.resize(); }); camera0 =new BABYLON.FreeCamera("FreeCamera", new BABYLON.Vector3(0, 0, -80), scene); camera0.attachControl(canvas, true); camera0.speed=0.5;//相机移动速度是默认速度的一半 camera0.minZ=0.01;//相机位置距前视锥截面的距离,也就是说到相机距离小于0.01的图元都不会显示,这个值不能过小,否则Babylon.js内置的鼠标选取将失效 camera0.layerMask=2;//相机的遮罩层次,这个相机将只能显示遮罩层次同为2的网格,如果不设置这个属性,似乎可以显示所有遮罩层次的网格 scene.activeCameras.push(camera0);//将相机加入活跃相机列表,默认情况下Babylon.js只使用一个活跃相机,但是也可以强行使用多个 light0 = new BABYLON.HemisphericLight("Hemi0", new BABYLON.Vector3(0, 1, 0), scene);//半球光源 //三个参照物,MeshBuilder是新版Babylon.js中使用的网格构建对象,之前翻译入门教程时还没有这个对象,它的特点是把一大堆参数统一整理到一个option参数中 var mesh_base=new BABYLON.MeshBuilder.CreateSphere("mesh_base",{diameter:1},scene); mesh_base.material=mat_green; mesh_base.position.x=0; mesh_base.layerMask=2; var mesh_base1=new BABYLON.MeshBuilder.CreateSphere("mesh_base1",{diameter:1},scene); mesh_base1.position.y=10; mesh_base1.position.x=0; mesh_base1.material=mat_green; mesh_base1.layerMask=2; var mesh_base2=new BABYLON.MeshBuilder.CreateSphere("mesh_base2",{diameter:1},scene); mesh_base2.position.y=-10; mesh_base2.position.x=0; mesh_base2.material=mat_green; mesh_base2.layerMask=2; 。 。 。 MyBeforeRender(); } function MyBeforeRender() { scene.registerBeforeRender(function() { if(scene.isReady()) { 。 。 。 。 。 。 } }); engine.runRenderLoop(function () { engine.hideLoadingUI(); if (divFps) { // Fps divFps.innerHTML = engine.getFps().toFixed() + " fps"; } scene.render(); }); } </script> </html>この 3D シーンには、簡単なテストに必要ないくつかの基本的な要素が含まれています。すべてのコンポーネントを含む Babylon.js ライブラリの非圧縮バージョンは次のとおりです。実際の使用時に帯域幅を節約するために、Babylon.js 公式 Web サイトが提供するツールを使用して、Babylon.js の合理化または圧縮バージョンをカスタマイズできます。ライブラリ
2. 基本的なグリッドを作成します
基本的なメッシュに対して頂点変換を行うことで、さまざまな単純なモデルを生成する予定です。 Babylon.js では、「ストリップ」は頂点変換に非常に適したメッシュ タイプです。ストリップの構築と変形に関するドキュメントは、公式 Babylon.js チュートリアルにあります (http://down.51cto.com/data/2449757)。
基本的なグリッドを確立するために使用されるコードは次のとおりです:
//下面这些函数都通过控制台调用 //在ZoY平面里建立一个圆环路径 //radius:半径,sumpoint:使用几个点 function MakeRing(radius,sumpoint) { var arr_point=[]; var radp=Math.PI*2/sumpoint; for(var i=0.0;i<sumpoint;i++) { var x=0; var rad=radp*i; //var y=sswr(radius*Math.sin(rad),null,5);//在这里需要降低一些精确度?否则Babylon.js在计算顶点数据时可能和这里不一致? //var z=sswr(radius*Math.cos(rad),null,5); var y=radius*Math.sin(rad); var z=radius*Math.cos(rad); arr_point.push(new BABYLON.Vector3(x,y,z)); } arr_point.push(arr_point[0].clone());//首尾相连,不能这样相连,否则变形时会多出一个顶点!!,看来这个多出的顶点无法去掉,只能在选取时额外处理它 return arr_point; } var arr_path=[];//核心数据 //arr_point:单个路径的点数组,xstartl:第一个扁平路径放在最左侧,spacing:路径的间距,sumpath:一共使用几条路径, function MakeRibbon(arr_point,xstartl,spacing,sumpath,name) {//将一条圆环路径扩展成相互平行的多个圆环路径,然后使用这些路径生成条带 arr_path=[]; for(var i=0;i<sumpath;i++)//对于每一条路径 { var x=xstartl+spacing*i; //var arr=arr_point.concat(null);//为什么拷贝失灵了? //var [ ...arr ] = arr_point;//ES6的新扩展运算符?-》也不好使,因为数组里的元素是指针?!! var len=arr_point.length; var arr=[]; for(var j=0;j<len;j++) { var obj=arr_point[j].clone(); obj.x=x; // arr.push(obj); } arr_path.push(arr); arr=null; } mesh_origin.dispose(); mesh_origin=BABYLON.MeshBuilder.CreateRibbon(name,{pathArray:arr_path,updatable:true,closePath:false,closeArray:false}); //mesh_origin=mesh;//用一个全局变量保存最终会被导出的mesh mesh_origin.sideOrientation=BABYLON.Mesh.DOUBLESIDE;//显示网格的前后两面 mesh_origin.material=mat_frame; mesh_origin.layerMask=2; }
プログラミング中に発生するいくつかの問題:
a. パスに設定されている座標値は、実際に表示されると若干変更される場合があります。たとえば、5 は 4.99999999999 になる場合があります。 , しかし、効果はないようです。
b、虽然圆环路径分成12段应该由12个顶点组成,但是如果只有12个顶点,那么在调用CreateRibbon方法时,如果把closePath参数设为true则Babylon.js会自动添加一个不受控制的顶点来闭合圆环路径,如果设为false,则路径无法闭合。为此在第18行再添加一个与起始点
重合的顶点使圆环路径闭合。
c、原计划直接使用数组的concat方法复制arr_point数组产生更多的圆环路径,但concat方法似乎只能对一维数组使用(栈?),而arr_point的每个元素都是一个BABYLON.Vector3对象,所以只好用for循环一个点一个点的生成路径
d、MakeRing是一个生成圆环路径的方法,使用者可以根据需要编写各种其他类型的路径生成方法。
3、调整网格的属性:
可以对网格的属性进行一些调整,但是这些调整只在这个编辑器里生效,并不会被导出。
比如对网格材质的调整:
function ChangeMaterial(mesh,mat) { mesh.material=mat; }
4、显示网格顶点法线方向
代码如下:
var lines_normal={}; /*ShowNormals(mesh_origin) DeleteMeshes([lines_normal]); * */ //显示所有的顶点法线 function ShowNormals(mesh) { //DeleteMeshes(arr_line_normal); if(lines_normal.dispose) { lines_normal.dispose(); } //遍历顶点 var vb=mesh.geometry._vertexBuffers; var data_pos=vb.position._buffer._data;//顶点数据 var data_mormal=vb.normal._buffer._data;//法线数据 var len=data_pos.length; var lines=[]; for(var i=0;i<len;i+=3) {//CreateLineSystem使用一个网格包含很多分立的线段(路径),CreateLines则是一条首尾相连的路径 // var vec=new BABYLON.Vector3(data_pos[i],data_pos[i+1],data_pos[i+2]); var vec2=vec.clone().add(new BABYLON.Vector3(data_mormal[i],data_mormal[i+1],data_mormal[i+2]).normalize().scale(1)); lines.push([vec,vec2]); } lines_normal=new BABYLON.MeshBuilder.CreateLineSystem("lines_normal",{lines:lines,updatable:false},scene); lines_normal.color=new BABYLON.Color3(1, 0, 0); }
Babylon.js内置的CreateLineSystem方法可以方便的建立一个由很多条线段组成的网格。
5、删除网格
代码如下:
function DeleteMeshes(arr)//假设一个数组里的都是mesh,彻底清空它 { var len=arr.length; for(var i=0;i<len;i++) { if(arr[i].dispose) arr[i].dispose(); } arr=[]; }
值得注意的是,直接在控制台里执行mesh.dispose()并不生效。
这里也体现出CreateLineSystem的方便之处——删除法线时只需要dispose一个对象。
6、鼠标移入时显示三角形信息
鼠标动作监听代码:
mesh_origin=new BABYLON.Mesh("mesh_origin",scene);//建立一个空的初始网格对象 mesh_surface=new BABYLON.Mesh("mesh_surface",scene); mesh_surface0=new BABYLON.Mesh("mesh_surface0",scene); if(true) {//初始化三个GUI标签 label_index1=new BABYLON.GUI.TextBlock(); label_index1.text = "label_index1"; label_index1.color="white"; label_index1.isVisible=false;//初始化时标签不可见 //label_index1.linkWithMesh(mesh_surface0);//TextBlock并不是顶层元素 advancedTexture.addControl(label_index1); label_index2=new BABYLON.GUI.TextBlock(); label_index2.text = "label_index2"; label_index2.color="white"; label_index2.isVisible=false; //label_index2.linkWithMesh(mesh_surface0); advancedTexture.addControl(label_index2); label_index3=new BABYLON.GUI.TextBlock(); label_index3.text = "label_index3"; label_index3.color="white"; label_index3.isVisible=false; //label_index3.linkWithMesh(mesh_surface0); advancedTexture.addControl(label_index3); } //监听鼠标移动 canvas.addEventListener("mousemove", function(evt){ var pickInfo = scene.pick(scene.pointerX, scene.pointerY,null,null,camera0);//如果同时有多个激活的相机,则要明确的指出使用哪个相机 //cancelPropagation(evt); //cancelEvent(evt); label_index1.isVisible=false; label_index2.isVisible=false; label_index3.isVisible=false; if(mesh_surface.dispose) { mesh_surface.dispose(); } if(mesh_surface0.dispose) { mesh_surface0.dispose(); } if(pickInfo.hit&&(pickInfo.pickedMesh.name=="mesh_origin"||pickInfo.pickedMesh.name=="mesh_ribbon"))//找到鼠标所在的三角形并重绘之 { //在鼠标所指的地方绘制一个三角形,表示被选中的三角形 var faceId=pickInfo.faceId; var pickedMesh=pickInfo.pickedMesh; var indices=[pickedMesh.geometry._indices[faceId*3] ,pickedMesh.geometry._indices[faceId*3+1],pickedMesh.geometry._indices[faceId*3+2]]; var vb=pickedMesh.geometry._vertexBuffers; var position=vb.position._buffer._data; var normal=vb.normal._buffer._data; var uv=vb.uv._buffer._data; var len=arr_path[0].length; var p1={index:indices[0],position:[position[indices[0]*3],position[indices[0]*3+1],position[indices[0]*3+2]] ,normal:[normal[indices[0]*3],normal[indices[0]*3+1],normal[indices[0]*3+2]] ,uv:[uv[indices[0]*2],uv[indices[0]*2+1]],ppi:("1:"+(Math.round(indices[0]/len)+0))+"-"+(indices[0]%len)};//pathpointindex var p2={index:indices[1],position:[position[indices[1]*3],position[indices[1]*3+1],position[indices[1]*3+2]] ,normal:[normal[indices[1]*3],normal[indices[1]*3+1],normal[indices[1]*3+2]] ,uv:[uv[indices[1]*2],uv[indices[1]*2+1]],ppi:("2:"+(Math.round(indices[1]/len)+0))+"-"+(indices[1]%len)}; var p3={index:indices[2],position:[position[indices[2]*3],position[indices[2]*3+1],position[indices[2]*3+2]] ,normal:[normal[indices[2]*3],normal[indices[2]*3+1],normal[indices[2]*3+2]] ,uv:[uv[indices[2]*2],uv[indices[2]*2+1]],ppi:("3:"+(Math.round(indices[2]/len)+0))+"-"+(indices[2]%len)}; 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]; var numm=2; mesh_surface0=newland.make_tryangle(p1,p2,p3,"mesh_surface1",scene);//使用三个点绘制一个三角形 mesh_surface0.material=mat_green; mesh_surface0.sideOrientation==BABYLON.Mesh.DOUBLESIDE; mesh_surface0.layerMask=2; label_index1.isVisible=true; //label_index1.layerMask=2;//这个属性对于gui被管对象并不生效? label_index1.text=p1.ppi;//ppi表示这个顶点是三角形的第几个顶点,以及这个顶点位于第几条路径的第几个位置 var pos1=new BABYLON.Vector3(p1.position[0],p1.position[1],p1.position[2]); label_index1.moveToVector3(pos1,scene); label_index1.itspos=pos1; label_index2.isVisible=true; label_index2.text=p2.ppi; var pos2=new BABYLON.Vector3(p2.position[0],p2.position[1],p2.position[2]); label_index2.moveToVector3(pos2,scene); label_index2.itspos=pos2; label_index3.isVisible=true; label_index3.text=p3.ppi; var pos3=new BABYLON.Vector3(p3.position[0],p3.position[1],p3.position[2]); label_index3.moveToVector3(pos3,scene); label_index3.itspos=pos3; //将三个点移动到场景的中心 pt=[(p1.position[0]-po[0])*numm,(p1.position[1]-po[1])*numm,(p1.position[2]-po[2])*numm]; p1.position=pt; pt=[(p2.position[0]-po[0])*numm,(p2.position[1]-po[1])*numm,(p2.position[2]-po[2])*numm]; p2.position=pt; pt=[(p3.position[0]-po[0])*numm,(p3.position[1]-po[1])*numm,(p3.position[2]-po[2])*numm]; p3.position=pt; //在场景的中心再绘制一个大一倍的三角形 mesh_surface=newland.make_tryangle(p1,p2,p3,"mesh_surface",scene); mesh_surface.material=mat_green; mesh_surface.sideOrientation==BABYLON.Mesh.DOUBLESIDE; mesh_surface.layerMask=1;//遮罩层次是1 } },false);
需要注意的有以下几点:
a、鼠标指向时显示的是这个点在arr_path中的索引,而不是在“顶点数据对象”中的索引!
b、最初的计划是像魔兽争霸3一样在窗口的左下角建立一个遮罩层次为1的小视口,用来特写被选中的三角形(mesh_surface),但是后来发现Babylon.js的GUI不能设置遮罩层次,也不能像鼠标选取一样设置相对于哪一个相机,所以取消了左下角的小视口。
如果需要添加视口,代码如下:
var mm = new BABYLON.FreeCamera("minimap", new BABYLON.Vector3(0,0,-20), scene); mm.layerMask = 1; var xstart = 0.0, ystart = 0.1;//意为占屏幕宽高的比例 var width = 0.2, height = 0.2; //视口边界从左下角开始 mm.viewport = new BABYLON.Viewport( xstart, ystart, width, height ); scene.activeCameras.push(mm);var mm = new BABYLON.FreeCamera("minimap", new BABYLON.Vector3(0,0,-20), scene); mm.layerMask = 1; var xstart = 0.0, ystart = 0.1;//意为占屏幕宽高的比例 var width = 0.2, height = 0.2; //视口边界从左下角开始 mm.viewport = new BABYLON.Viewport( xstart, ystart, width, height ); scene.activeCameras.push(mm);
c、需要注意的是GUI是一次性绘制在视口上的,默认情况下即使相关的网格位置发生变化,GUI仍然会保持在视口上最初的位置。linkWithMesh方法可以将GUI和网格绑定起来,但是似乎只能对部分“底层的”GUI类型生效,为了使得标签跟随顶点移动,需要手动在每一帧渲染之前更新标签的位置:
scene.registerBeforeRender(function() { if(scene.isReady()) { if(label_index1.isVisible==true)//不断刷新gui的位置 {//在具有多个激活相机时选择了错误的参考相机?-》会强制选择最新建立的相机 label_index1.moveToVector3(label_index1.itspos,scene); label_index2.moveToVector3(label_index2.itspos,scene); label_index3.moveToVector3(label_index3.itspos,scene); } } });
7、选取顶点:
3DsMax和Blender(Babylon.js库在3D模型部分参考了Blender的许多设计)都使用鼠标在网格中选取需要修改的区域,我不是很习惯这种方式,所以考虑用JS函数代替鼠标操作:
var lines_inpicked={};//线段系统对象,表示所有被选中点影响的线段 var arr_ij=[];//记录被选中的点在arr_path中的索引的数组 /*DeleteMeshes([lines_inpicked]); * PickPoints([[0,0],[0,12],[1,1]],mesh_origin) * */ //选定一些顶点 function PickPoints(arr,mesh) { if(arr_path.length==0) { alert("尚未生成路径数组!"); return; } if(lines_inpicked.dispose) { lines_inpicked.dispose(); } //arr_ij=[]; //为了后面考虑,需要先对arr整体遍历一遍,把首尾接口处关联起来 arr_ij=arr;//把路径和点的位置记录下来 var vb=mesh.geometry._vertexBuffers; var data_pos=vb.position._buffer._data; var len_pos=data_pos.length; var data_index=mesh.geometry._indices; var len_index=data_index.length; var len=arr.length; var lines=[]; for(var i=0;i<len;i++)//对于每一个需要显示的被选择点 { var int0=arr[i][0]; var int1=arr[i][1]; var vec=arr_path[int0][int1];//获取到路径数组中的一个Vector3对象 //假设路径数组和顶点数据是一一对应的?同时假设每一条路径的长度都和第一条相同 var arr_index=[int0*arr_path[0].length+int1];//所有位于所选位置的顶点在顶点数据对象中的索引 //下面遍历网格的绘制索引,找出这个被选中的顶点每一次的使用情况 for(var j=0;j<len_index;j+=3)//根据顶点在三角形中的位置分三种情况讨论 {//绘制出和这个顶点相关的所有线段,这样绘制会有很严重的线段重合!观察是否对性能有很大的影响 var len2=arr_index.length; for(var k=0;k<len2;k++) { if(arr_index[k]==data_index[j])//三角形的第一个顶点 {//把这个顶点和三角形中的另两个顶点用黄线连起来 var num2=data_index[j+1]*3; var num3=data_index[j+2]*3; var vec2=new BABYLON.Vector3(data_pos[num2],data_pos[num2+1],data_pos[num2+2]); var vec3=new BABYLON.Vector3(data_pos[num3],data_pos[num3+1],data_pos[num3+2]); lines.push([vec,vec2]); lines.push([vec,vec3]); } else if(arr_index[k]==data_index[j+1])//三角形的第一个顶点 { var num2=data_index[j]*3; var num3=data_index[j+2]*3; var vec2=new BABYLON.Vector3(data_pos[num2],data_pos[num2+1],data_pos[num2+2]); var vec3=new BABYLON.Vector3(data_pos[num3],data_pos[num3+1],data_pos[num3+2]); lines.push([vec,vec2]); lines.push([vec,vec3]); } else if(arr_index[k]==data_index[j+2])//三角形的第一个顶点 { var num2=data_index[j]*3; var num3=data_index[j+1]*3; var vec2=new BABYLON.Vector3(data_pos[num2],data_pos[num2+1],data_pos[num2+2]); var vec3=new BABYLON.Vector3(data_pos[num3],data_pos[num3+1],data_pos[num3+2]); lines.push([vec,vec2]); lines.push([vec,vec3]); } } } } lines_inpicked=new BABYLON.MeshBuilder.CreateLineSystem("lines_normal",{lines:lines,updatable:false},scene); lines_inpicked.color=new BABYLON.Color3(1, 1, 0); } //需要一些选择选定顶点的方法
有以下几处需要注意:
a、因为MakeRing生成的路径里包括两个重合的点(首尾),为了保证变形后的网格仍然连续,在选择时这两个重合的点必须同时被选中,经过考虑,规定这个确保首尾重合点同时选中的操作在生成arr数组参数时进行。
b、因为基础网格mesh_origin的每一个顶点恰好位置不同,所以一开始我以为“与被选中的点的位置相同的点”应该具有与被选中点相同的变形效果,但是事实上经过网格变形后,完全可能出现两个不相干的顶点位于同一位置的情况,这时应用相同的变形效果会出现错误,比如人可以把手放在腿上,这时手和腿的接触点具有相同的位置,但如果因此对这两个点应用相同的变形效果,就很诡异了。同时如前文所说,有时arr_path中存储的位置和顶点数据中存储的位置可能出现微小的偏差。
最后采取的方法是将arr_path中的数组转化为顶点数据对象中的数组。
c、这里直接选取了几个点进行变形,还需要编写一些按照某种规则批量选取点的方法,也希望大家能帮我想一想怎样选取顶点比较方便。
8、网格变形:
代码如下:
/* * TransVertex(mesh_origin,arr_ij,BABYLON.Matrix.RotationX(Math.PI/2)) *TransVertex(mesh_origin,arr_ij,BABYLON.Matrix.Translation(0.5,0,0)) * */ //移动选定的顶点,同时更新网格、选取线、法线 function TransVertex(mesh,arr,matrix) { var len=arr.length; for(var i=0;i<len;i++)//移动路径数组里的每个顶点 { //var vec=arr_path[arr[i][0]][arr[i][1]]; arr_path[arr[i][0]][arr[i][1]]=BABYLON.Vector3.TransformCoordinates(arr_path[arr[i][0]][arr[i][1]],matrix); } //更新网格,发现设置了closePath:true之后在顶点数据里还是会自动补上一个接续点,这还不如一开始自己添加!起码自己添加可以保证路径数组和顶点数据的一致性!!!! mesh=BABYLON.MeshBuilder.CreateRibbon(mesh.name,{pathArray:arr_path,updatable:true,instance:mesh,closePath:false,closeArray:false}); //上面的更新重绘是默认重算法线方向的,这与直接修改顶点数据不同 //清空法线 DeleteMeshes([lines_normal]); //更新选取顶点表示 PickPoints(arr,mesh); } //需要一些生成变化矩阵的方法
其实这里移动的顶点和前面选择的点没有关系,不选择也可以直接变形,只不过不会有黄线显示罢了。这里也同样需要一些生成更复杂的变换矩阵的方法。
9、导出:
导出方法的调用如下:
/*ExportMesh("1",mat_blue)*/ function ExportMesh(filename,mat) { try{ newland.ExportBabylonMesh([mesh_origin],filename,mat); } catch(e) { console.error(e) } }
函数的第一个参数是导出后的文件名(不包括扩展名),第二个参数是网格使用的材质对象(暂时只支持简单的颜色材质)。
其中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属性强制转化为数组类型:
//将TypedArray转化为普通array newland.BuffertoArray2=function(arr) { var arr2=[]; var len=arr.length; for(var i=0;i<len;i++) { arr2.push(arr[i]); } return arr2; }
对于旧版的Babylon.js应该可以不加修改的使用这个方法,因为typedArray也具有数组的大部分功能。
10、导入:
在另一个页面里实现模型导入功能,这个页面同样在“基础场景”的基础上编写,其中导入方法如下:
function ImportMesh(objname,filepath,filename) { BABYLON.SceneLoader.ImportMesh(objname, filepath, filename, scene , function (newMeshes, particleSystems, skeletons) {//载入完成的回调函数 if(mesh_origin&&mesh_origin.dispose) { mesh_origin.dispose(); } mesh_origin=newMeshes[0]; //mesh_origin.material=mat_frame; //mesh_origin.layerMask=2; } ); }
相关推荐:
以上がChrome コンソールを使用して 3D モデルを編集する方法の実装 (コード)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。