UNPKG

pbrtools

Version:
605 lines (566 loc) 21.5 kB
import { BufferReader,BufferWriter } from './buffer'; import * as fs from 'fs'; import * as path from 'path'; import {allfiles } from './util' import { MeshGeo } from './meshGeo'; //Version 2 class data_block2{ offofData=0; //文档说是相对内容区,实际是相对于整个文件 length = 0; constructor(off:number,len:number){ this.offofData=off; this.length=len; } } class Mtl_block2{ clsName=''; shaderName=''; mtlPath=''; } class BindBoneInfo2{ _bones:string[]=[]; _bonePose:Float32Array; _invgBonePose:Float32Array; } class MeshInfo2{ _name=''; _vbInfos:{_vb:Float32Array,_attrib:string}[]=[]; _ib:Uint16Array; //现在只有一个ib } class SubmeshInfo2{ vbIdx=0;//short vbstart=0;//int32 vblen=0;//int32 ibstart=0; //int32; iblen=0; //int32 drawCnt=0;//short subs:{subibstart:number,subiblen:number,bonedic:Uint8Array}[]=[]; } class VertexAttrib{ attribs:string[]; static compSz={ POSITION:{sz:12,type:'float',num:3}, NORMAL:{sz:12,type:'float',num:3}, COLOR: {sz:16,type:'float',num:4}, UV:{sz:8,type:'float',num:2}, UV1:{sz:8,type:'float',num:2}, BLENDWEIGHT:{sz:16,type:'float',num:4}, BLENDINDICES:{sz:16,type:'int32',num:4}, TANGENT:{sz:12,type:'float',num:3}, BINORMAL:{sz:12,type:'float',num:3} } constructor(attrib:string){ this.attribs = attrib.split(/[,;:]/); } getVertexSize(){ let sz =0; this.attribs.forEach((v)=>{ if(VertexAttrib.compSz[v]){ sz += VertexAttrib.compSz[v].sz; }else{ throw 'unknown vertex component :'+v; } }); return sz; } hasAttrib(attr:string){ return this.attribs.indexOf(attr)>=0; } getOff(attr:string){ let off=0; for(let i=0; i<this.attribs.length; i++){ let cattr = this.attribs[i]; if(cattr==attr){ return off; } off+=VertexAttrib.compSz[cattr].sz; } return -1; } } class loader_lh2 extends BufferReader{ _dataoff=0;//数据区偏移 _strings:string[] = []; _datablocks:data_block2[]=[]; _mtls:Mtl_block2[]=[]; _meshInfo=new MeshInfo2(); _bindInfo=new BindBoneInfo2(); _subMeshes:SubmeshInfo2[]=[]; _attrReg = new RegExp("(\\w+)|([:,;])", "g"); _shaderAttributes:string[]; _cursubmeshid=0; constructor(){ super(); } load(str:string){ var data = fs.readFileSync(str); this.parse(data.buffer); } parse(data:ArrayBuffer):any{ this.init(data); let version=this.readString(); //区块信息 this._dataoff = this.readU32(); let len = this.readU32(); let cnt = this.readU16(); for( let i=0; i<cnt; i++){ this._datablocks.push(new data_block2(this.readU32(),this.readU32())); } //字符串 let strOff = this.readU32(); let strCnt = this.readU16(); this.seek(this._dataoff+strOff); for( let i=0; i<strCnt; i++){ this._strings.push( this.readString()); } //内容区 this._datablocks.forEach((v,i)=>{ this.seek(v.offofData); let strid = this.readU16(); var str = this._strings[strid]; console.log('--- '+str); var func = this['read_'+str]; if(func){ func.call(this); }else{ console.log('unknown block '+str); } }); console.log('ok'); } read_MATERIAL(){ let mtlinfo = new Mtl_block2(); mtlinfo.clsName = this._strings[this.readU16()]; mtlinfo.shaderName = this._strings[this.readU16()]; mtlinfo.mtlPath = this._strings[this.readU16()]; this._mtls.push(mtlinfo); } read_MESH(){ this._meshInfo._name = this._strings[this.readU16()]; //VB let vbCnt = this.readU16(); for(let i=0; i<vbCnt; i++){ let vbstart = this.readU32(); let vblen = this.readU32(); let attrib = this._strings[this.readU16()]; let cvbinfo:{_vb:ArrayBuffer,_attrib:string}={_vb:null,_attrib:''}; let vb:Float32Array=null; if((this._dataoff+vbstart)%4!=0){ //没有按照4对齐 let vbdt = new Uint8Array(this.buff, this._dataoff+vbstart, vblen); vb = new Float32Array(vblen/4); (new Uint8Array(vb.buffer)).set(vbdt); }else{ vb = new Float32Array(this.buff, this._dataoff+vbstart, vblen/4); } this._meshInfo._vbInfos.push({_vb:vb,_attrib:attrib}); } //IB let ibstart = this.readU32(); let iblen = this.readU32(); if((this._dataoff+ibstart)%2!=0){ let ibdt = new Uint8Array(this.buff,this._dataoff+ibstart,iblen); this._meshInfo._ib = new Uint16Array(iblen/2); (new Uint8Array(this._meshInfo._ib.buffer)).set(ibdt); }else{ this._meshInfo._ib = new Uint16Array(this.buff,this._dataoff+ibstart,iblen/2); } //bones let boneCnt = this.readU16(); for( let i=0; i<boneCnt; i++){ let bonename = this._strings[this.readU16()]; this._bindInfo._bones.push(bonename); } let bindPoseStart = this.readU32(); let bindPoseLen = this.readU32(); let bpdt = new Uint8Array(this.buff, this._dataoff+bindPoseStart, bindPoseLen); this._bindInfo._bonePose = new Float32Array(bindPoseLen/4); (new Uint8Array(this._bindInfo._bonePose.buffer)).set(bpdt); let inverseGlobalBindPosesStart = this.readU32(); let inverseGlobalBindPosesLength = this.readU32(); let igPose = new Uint8Array(this.buff, this._dataoff+inverseGlobalBindPosesStart, inverseGlobalBindPosesLength); this._bindInfo._invgBonePose = new Float32Array(inverseGlobalBindPosesLength/4); (new Uint8Array(this._bindInfo._invgBonePose.buffer)).set(igPose); } read_SUBMESH(){ let sub = new SubmeshInfo2(); sub.vbIdx = this.readU16(); sub.vbstart = this.readU32(); sub.vblen = this.readU32(); sub.ibstart = this.readU32(); sub.iblen = this.readU32(); sub.drawCnt = this.readU16(); for(let i=0; i<sub.drawCnt; i++){ let subsub = { subibstart:this.readU32(), subiblen:this.readU32(), bonedic:new Uint8Array(this.buff,this._dataoff+this.readU32(),this.readU32()) }; sub.subs.push(subsub); } this._subMeshes.push(sub); } getStr(id:number):string{ return this._strings[id]; } getOrAddString(str:string):number{ let r = this._strings.indexOf(str); if(r<0){ r=this._strings.length; this._strings.push(str); } return r; } save(file:string){ //先清空字符串 this._strings=[]; this._mtls.length=0; let blockcnt = this._mtls.length + 1 + //mesh this._subMeshes.length; this._datablocks.length=0; //计算头部分的大小 let verstr = 'LAYAMODEL:03'; let headlen = verstr.length+2; headlen += (4+4+2+blockcnt*8+4+2); this._dataoff=headlen; let headbuff = new BufferWriter(headlen); //头先不写,最后在写 let datapos = 0;//只是数据区偏移,不是整体 //写材质区 let mtlblockw = new BufferWriter(this._mtls.length*8); let mtlnameid = this.getOrAddString('MATERIAL'); this._mtls.forEach((v)=>{ let cblk=new data_block2(datapos+this._dataoff,8); this._datablocks.push(cblk); mtlblockw.wu16(mtlnameid); mtlblockw.wu16(this.getOrAddString(v.clsName)); mtlblockw.wu16(this.getOrAddString(v.shaderName)); mtlblockw.wu16(this.getOrAddString(v.mtlPath)); datapos+=8; }); //写mesh let meshsz = 2+//blockname 2+//name 2+this._meshInfo._vbInfos.length*(4+4+2)+ 4+//ibstart 4+//iblen 2+//bonecnt 2*this._bindInfo._bones.length+// 4+//bind pose start 4+//len 4+//invg start 4;//len let meshblockw = new BufferWriter(meshsz); this._datablocks.push(new data_block2(datapos+this._dataoff,meshsz)); datapos+=meshsz; meshblockw.wu16(this.getOrAddString('MESH')) .wu16(this.getOrAddString(this._meshInfo._name)) .wu16(this._meshInfo._vbInfos.length); let vbsz=0; this._meshInfo._vbInfos.forEach((v)=>{ meshblockw.wu32(datapos) .wu32(v._vb.byteLength) .wu16(this.getOrAddString(v._attrib)); datapos+=v._vb.byteLength; vbsz += v._vb.byteLength; }); //ib meshblockw.wu32(datapos);//IB实际位置 meshblockw.wu32(this._meshInfo._ib.byteLength); datapos+=this._meshInfo._ib.byteLength; //bone meshblockw.wu16(this._bindInfo._bones.length); this._bindInfo._bones.forEach((v)=>{ meshblockw.wu16(this.getOrAddString(v)); }); //bindpose meshblockw.wu32(datapos); meshblockw.wu32(this._bindInfo._bonePose.byteLength); datapos+=this._bindInfo._bonePose.byteLength; //invg meshblockw.wu32(datapos); meshblockw.wu32(this._bindInfo._invgBonePose.byteLength); datapos += this._bindInfo._invgBonePose.byteLength; //mesh对应的数据 let meshdatasz = vbsz+this._meshInfo._ib.byteLength+ this._bindInfo._bonePose.byteLength+ this._bindInfo._invgBonePose.byteLength; let meshdatablockw = new BufferWriter(meshdatasz); this._meshInfo._vbInfos.forEach((v)=>{ meshdatablockw.wab(v._vb.buffer,v._vb.byteLength,v._vb.byteOffset); }); let _ib = this._meshInfo._ib; meshdatablockw.wab(_ib.buffer,_ib.byteLength,_ib.byteOffset); meshdatablockw.wab(this._bindInfo._bonePose.buffer); meshdatablockw.wab(this._bindInfo._invgBonePose.buffer); //submesh let submeshsz = 0; this._subMeshes.forEach((v)=>{ let cursz = 2+//blockname 2+//vbidx 4+//vbstart 4+//vblen 4+//ibstart 4+//iblen 2+//drawcnt v.drawCnt*16; submeshsz += cursz; this._datablocks.push(new data_block2(datapos+this._dataoff,cursz)); datapos+=cursz; //完成后指向了submesh的最后部分 }); let submeshblockw = new BufferWriter(submeshsz); let bonedicsz = 0; this._subMeshes.forEach((v)=>{ submeshblockw.wu16(this.getOrAddString('SUBMESH')) .wu16(v.vbIdx) .wu32(v.vbstart) .wu32(v.vblen) .wu32(v.ibstart) .wu32(v.iblen) .wu16(v.drawCnt); for( let i=0; i<v.drawCnt; i++){ let dicsz = v.subs[i].bonedic.byteLength; bonedicsz += dicsz; submeshblockw.wu32(v.subs[i].subibstart) .wu32(v.subs[i].subiblen) .wu32(datapos) .wu32(dicsz); datapos+=dicsz; } }); //bonedic let bonedicw = new BufferWriter(bonedicsz); this._subMeshes.forEach((v)=>{ v.subs.forEach((sv)=>{ bonedicw.wab(sv.bonedic.buffer,sv.bonedic.byteLength, sv.bonedic.byteOffset); }); }); let strpos = datapos; //为了方便,字符串最后再写 let strsw = new BufferWriter(0); strsw.nowrite=true;//先计算大小 this._strings.forEach((str)=>{ strsw.wstr(str); }); //然后真写 let strbuffsz = strsw._writePos; strsw = new BufferWriter(strbuffsz); this._strings.forEach((str)=>{ strsw.wstr(str); }); //整合 let alldatalen = //mtlblockw.buff.byteLength + meshblockw.buff.byteLength + meshdatablockw.buff.byteLength + submeshblockw.buff.byteLength + bonedicw.buff.byteLength + strsw.buff.byteLength; headbuff.wstr(verstr); headbuff.wu32(headlen) .wu32(alldatalen) //link data lenth .wu16(blockcnt); this._datablocks.forEach((v)=>{ headbuff.wu32(v.offofData) .wu32(v.length); }); headbuff.wu32(strpos);//stroff headbuff.wu16(this._strings.length);//strcnt let out = new BufferWriter(headbuff.buff.byteLength + alldatalen); out.wab(headbuff.buff); //out.wab(mtlblockw.buff); out.wab(meshblockw.buff); out.wab(meshdatablockw.buff); out.wab(submeshblockw.buff); out.wab(bonedicw.buff); out.wab(strsw.buff); fs.writeFileSync(file,new Buffer(new Uint8Array(out.buff))); } } //TEST /* var cc = new loader_lh2(); cc.load('E:/layaair/layaair/publish/LayaAirPublish/samples/res/threeDimen/skinModel/dude/dude-him.lm'); try{ cc.save('d:/temp/bb.lm'); }catch(e){ console.log(e); } */ //TEST function handlelm(lm:loader_lh2,modelcnfg:Object,swapyz:boolean):boolean{ lm._mtls.forEach((mtl,i)=>{ if(modelcnfg && modelcnfg[mtl.mtlPath]){ lm._mtls[i].mtlPath=modelcnfg[mtl.mtlPath]; } else{ lm._mtls[i].mtlPath = lm._mtls[i].mtlPath.replace('.lmat','.lpbr'); } console.log(lm._mtls[i].mtlPath); }); lm._meshInfo._vbInfos.forEach((v)=>{ let attrib = new VertexAttrib(v._attrib); let vertstride = attrib.getVertexSize(); let oldfstride=vertstride/4; let vertnum = v._vb.byteLength/vertstride; let mg = new MeshGeo(v._vb, lm._meshInfo._ib, vertstride/4, attrib.getOff('UV')/4,''); //BBX var minx=1e6,miny=1e6,minz=1e6,maxx=-1e6,maxy=-1e6,maxz=-1e6; for( let i=0; i<vertnum; i++){ var stp = i*oldfstride; var posx=v._vb[stp++]; var posy=v._vb[stp++]; var posz=v._vb[stp++]; if(posx<minx)minx=posx;if(posx>maxx)maxx=posx; if(posy<miny)miny=posy;if(posy>maxy)maxy=posy; if(posz<minz)minz=posz;if(posz>maxz)maxz=posz; stp=i*oldfstride; } console.log('submesh size:[',minx,miny,minz,maxx,maxy,maxz,']'); let oldvb = v._vb; var coldi=0 //缩放 if( modelcnfg && modelcnfg['scale']!=undefined ){ var scale = modelcnfg['scale']*1.0; for( let i=0; i<vertnum; i++){ var stp = i*oldfstride; var posx=oldvb[stp++]; var posy=oldvb[stp++]; var posz=oldvb[stp++]; if(posx<minx)minx=posx;if(posx>maxx)maxx=posx; if(posy<miny)miny=posy;if(posy>maxy)maxy=posy; if(posz<minz)minz=posz;if(posz>maxz)maxz=posz; stp=i*oldfstride; oldvb[stp++]=posx*scale;oldvb[stp++]=posy*scale;oldvb[stp++]=posz*scale; } } //TODO 是否交换yz if(swapyz){ console.log('ATTENTION! swap y z now! '); let coldi =0; for( let i=0; i<vertnum; i++){ var stp = i*oldfstride; var posx=oldvb[stp++]; var posy=oldvb[stp++]; var posz=oldvb[stp++]; var normx=oldvb[stp++]; var normy=oldvb[stp++]; var normz=oldvb[stp++]; stp=i*oldfstride; oldvb[stp++]=posx;oldvb[stp++]=posz;oldvb[stp++]=-posy; oldvb[stp++]=normx;oldvb[stp++]=normz;oldvb[stp++]=-normy; } } //计算tangent //目前只处理两个都没有的 let docalctan=true; if(v._attrib === 'POSITION,NORMAL,UV'){ console.log('111', v._attrib); let newattrib = 'POSITION,NORMAL,TANGENT,BINORMAL,UV'; v._attrib = newattrib; }else if( v._attrib == 'POSITION,NORMAL,UV,BLENDWEIGHT,BLENDINDICES'){ console.log('222', v._attrib); let newattrib = 'POSITION,NORMAL,TANGENT,BINORMAL,UV,BLENDWEIGHT,BLENDINDICES'; v._attrib = newattrib; } else if (v._attrib == 'POSITION,NORMAL,UV,UV1,BLENDWEIGHT,BLENDINDICES') { console.log('112', v._attrib); let newattrib = 'POSITION,NORMAL,TANGENT,BINORMAL,UV,UV1,BLENDWEIGHT,BLENDINDICES'; v._attrib = newattrib; } else{ docalctan=false; console.log( 'error:vetext has tangent. do nothing'); } if( docalctan){ let newfstride = oldfstride+6; var tb = mg.calcTangent(); var vb = v._vb = new Float32Array(vertnum*newfstride); var cnewi=0; for( let i=0; i<vertnum; i++){ vb[cnewi++]=oldvb[coldi++];vb[cnewi++]=oldvb[coldi++];vb[cnewi++]=oldvb[coldi++];//pos vb[cnewi++]=oldvb[coldi++];vb[cnewi++]=oldvb[coldi++];vb[cnewi++]=oldvb[coldi++];//normal vb[cnewi++]=tb.tan[i*3];vb[cnewi++]=tb.tan[i*3+1];vb[cnewi++]=tb.tan[i*3+2];//tan vb[cnewi++]=tb.binor[i*3];vb[cnewi++]=tb.binor[i*3+1];vb[cnewi++]=tb.binor[i*3+2];//bin //剩下的 for(var li=0; li<(oldfstride-6); li++){ vb[cnewi++]=oldvb[coldi++]; } } console.log('calctangent ok'); } }); return true; } var srcPath = 'E:/layaair/layaair/publish/LayaAirPublish/samples/res/threeDimen/skinModel/dude'; function dowork(){ var modelCnfgObj={}; try{ var modelcnfg = fs.readFileSync(srcPath+'/config.json','utf8'); if(modelcnfg){ modelCnfgObj = JSON.parse(modelcnfg); } }catch(e){ console.log('no config file:config.json'); } allfiles(srcPath,(file)=>{ if(path.extname(file).toLowerCase()=='.lm'){ if(path.extname(file.substr(0,file.length-3)).toLowerCase()=='.lm'){ return; } var cc = new loader_lh2(); try{ cc.load(file); }catch(e){ console.log(e); return; } var rfile = path.basename(file); var filepath = path.dirname(file)+'/'; //材质相关 cc._mtls.forEach((v,i)=>{ var lmat = filepath+v.mtlPath; var lpbr = filepath+v.mtlPath.replace('.lmat','.lpbr'); var colorfile=''; var normalfile=''; var meshname=''; if(!fs.existsSync(lpbr)){ if(fs.existsSync(lmat)){ try{ var lmatobj = JSON.parse(fs.readFileSync(lmat,'utf8')) as Object; try{ colorfile = lmatobj['customProps']['diffuseTexture']['texture2D'] }catch(e){} try{ normalfile = lmatobj['customProps']['normalTexture']['texture2D'] }catch(e){} try{ meshname = lmatobj['props']['name']; }catch(e){} }catch(e){} } var pbrfilec=`{ "version":"LAYAMATERIAL:01", "type": "PBRMaterial", "props": { "name": "${meshname}", "renderMode": 1, "has_tangent":true, "textures":[ {"name":"diffuseTexture","path":"${colorfile}"}, {"name":"normalTexture","path":"${normalfile}"} ] } }`; fs.writeFileSync(lpbr,pbrfilec,'utf8'); } }); //修改mesh if(handlelm(cc,modelCnfgObj[rfile],false)){ cc.save(file+'.lm'); }else{ } } },false); } dowork();