이 글은 3D 모델 편집을 위해 Chrome 콘솔을 사용하는 방법에 대한 구현(코드)을 공유합니다. 필요한 친구들이 참고할 수 있기를 바랍니다.
서문: 3D 모델 편집의 핵심은 정점 위치와 텍스처 색상을 편집하는 것입니다. 본 연구의 목적은 프로그래밍을 통해 모델을 직접 편집하는 방법을 찾는 것입니다. 이 편집 방법은 마우스로 클릭하고 드래그하는 일반적인 편집 방법과 유사합니다. 메소드 간의 관계는 프런트엔드 프로그래밍에서 "프로그래머가 정적 웹 페이지 작성"과 "아티스트가 웹 페이지 자르기" 간의 관계와 매우 유사합니다.
1. 도구 사용법:
1. 장면 세계 좌표계(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이고, 총 11개의 링이 있는 원통형 표면입니다.
확대하여 보기:
3. 빨간색 선 세그먼트로 각 꼭지점의 법선 방향을 표시하려면 ShowNormals(mesh_origin)를 입력하고 모든 법선을 삭제하려면
DeleteMeshes([lines_normal])를 입력하고 다음을 입력합니다. DeleteMeshes([mesh_origin])은 원통형 메시를 삭제합니다.
4. 그리드의 삼각형으로 마우스를 이동하면 삼각형의 정점 정보가 표시됩니다.
여기서 "1:2-5"는 이것이 삼각형의 첫 번째 정점임을 의미하며, 이 정점은 링(세 번째 링)에서 인덱스가 2인 원에 위치하며 링에서 이 정점의 인덱스는 5(즉, 여섯 번째 정점)입니다.
5. PickPoints([[2,5],[3,5],[2,6]],mesh_origin)를 입력하여 정점을 선택하세요
선택한 정점의 영향을 받는 모든 경계선은 노란색으로 표시됩니다. , 이 "선택"은 모양만 변경합니다.
6. 선택한 정점 10을 z축의 음수 방향으로 이동하려면 TransVertex(mesh_origin,arr_ij,BABYLON.Matrix.Translation(0,0,-10))을 입력합니다. 이전에 선택한 정점. 여기서 arr_ij는 인덱스 배열 [[2,5],[3,5],[2,6]]로 직접 대체될 수도 있습니다.
다른 유형의 변형은 TransVertex(mesh_origin,arr_ij,BABYLON.Matrix.RotationX(Math.PI/2))를 입력하여 달성할 수 있으며, 이는 선택한 정점을 X를 중심으로 90도 회전할 수 있습니다.
DeleteMeshes([lines_inpicked])를 입력하여 선택한 효과를 취소하고, ChangeMaterial(mesh_origin,mat_blue)을 입력하여 테두리를 파란색 텍스처로 변경합니다.
변형된 효과를 볼 수 있으며 계속해서 선택할 수 있습니다. vertices 및 변환
7. ImportMesh("1",mat_blue)를 입력하고 바빌론 모델 파일을 txt 형식으로 내보냅니다. 파일 이름은 "1.txt"입니다.
8. 내보낸 txt의 이름을 9.babylon으로 바꿉니다. 웹사이트 디렉토리에 들어가서 모델 미리보기 페이지 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>
//下面这些函数都通过控制台调用 //在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; }
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; } ); }
相关推荐:
위 내용은 3D 모델 편집을 위해 Chrome 콘솔을 사용하는 방법 구현(코드)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!