pbrtools
Version:
laya pbr tools
605 lines (566 loc) • 21.5 kB
text/typescript
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();