pbrtools
Version:
laya pbr tools
621 lines (570 loc) • 21.5 kB
text/typescript
import { MeshGeo } from './meshGeo';
import { BufferReader,BufferWriter } from './buffer';
import {allfiles } from './util'
import * as fs from 'fs';
import * as path from 'path';
class Laya_SubMesh{
attribs='POSITION:3,32,0;NORMAL:3,32,12;UV:2,32,24;'
_vertexBuffer:Float32Array;
_indexBuffer:Uint16Array;
_boneindicesBuffer:Uint8Array;//uint8
}
class Laya_Mesh{
version='LAYASKINANI:01';
name='MESH';
materials:string[]=[]; //只用字符串表示对应的材质文件或者材质id。有几个就表示有几个submesh
_bindPoses:Float32Array;//Matrix44[]
_inverseBindPoses:Float32Array;//Matrix44[]
submeshes:Laya_SubMesh[]=[];
}
class _submeshInfo{
iboff=0;
ibsize=0;
vboff=0;
vbsize=0;
boneidxoff=0;
boneidxsize=0;
}
export class Laya_Mesh_W extends BufferWriter{
mesh:Laya_Mesh;
_strmap:string[]=['BLOCK', 'DATA', 'STRINGS'];
dataOff=0;
dataSize=0;
strsOff=0;
strsNum=0; //
bindPoseOff=0;
bindPoseSize=0;
invGBindePoseOff=0;
invGBindePoseSize=0;
submeshInfo:_submeshInfo[]=[];
constructor(){
super();
}
/**
* 返回写了多大。
*/
outobj(obj,members:string,buff:ArrayBuffer,off:number):number{
if(!obj._memtype){
throw '对象中必须有_memtype 描述';
}
var mems = members.split(',');
var dview = new DataView(buff,off);
var moff=0;
mems.forEach((mname)=>{
var tp = obj._memtype[mname];
if(!tp)
throw 'no type info of member '+mname;
var value = obj[mname];
if( value==undefined)
throw 'no this member :'+mname;
var isarr = value instanceof Array;
var len = isarr?value.length:0;
if(isarr)throw 'outobj not implements array';
switch(tp){
case 'u8':
dview.setUint8(moff,value);moff+=1; break;
case 'u16':
dview.setUint16(moff,value,true);moff+=2; break;
case 'u32':
dview.setUint32(moff,value,true);moff+=4; break;
case 'f32':
dview.setFloat32(moff,value,true);moff+=4; break;
}
});
return moff;
}
outStrings(strs:string[]){
}
saveAsLm(file:string){
this.submeshInfo= [];
this.mesh.submeshes.forEach((v,i)=>{
this.submeshInfo[i]=new _submeshInfo()
});
//先空跑一下,组装字符表,统计大小
this.nowrite=true;
this._saveAsLm();
var sz = this._writePos;
this.buff = new ArrayBuffer(sz);
this.datav=new DataView(this.buff);
//真正写buffer
this.nowrite=false;
this._writePos=0;
this._saveAsLm();
fs.writeFileSync(file,new Buffer(new Uint8Array(this.buff)));
}
_saveAsLm(){
this.wstr(this.mesh.version)
.wu16(0)
.wu16(5+this.mesh.materials.length+this.mesh.submeshes.length);//chunk count
//0
//data chunk
//'DATA'
this.wstrid('DATA')//data chunk id
.wu32(this.dataOff)
.wu32(this.dataSize);
//var dataC={id:1,dataoff:0,datasize:0,_memtype:{id:'u16',dataoff:'u32',datasize:'u32'}};
//this.outobj(dataC,'id,dataoff,datasize',this.buff,this._writePos);
//1
//strings chunk
//'STRINGS'
this.wstrid('STRINGS') //strings chunk id
.wu16(this.strsOff)
.wu16(this.strsNum);
//把所有的string以wstr的方式写到对应区域
//2
//material chunk
//'MATERIAL'
this.mesh.materials.forEach((mtlname,i)=>{
this.wstrid('MATERIAL') //id
this.wu16(i); //放到_materials数组的位置
this.wstrid('SIMPLE')//shader name /没有用
.wstrid(mtlname);//url
});
//其他的material
//this.wu16(3);//相同块的id相同
//3
//mesh chunk
//'MESH'
this.wstrid('MESH') //id
.wstrid(this.mesh.name) //name
.wu32(this.bindPoseOff)//bindpose start
.wu32(this.bindPoseSize)//bindpose size
.wu32(this.invGBindePoseOff)//invGBindPose start
.wu32(this.invGBindePoseSize);//invGBindPose size
//写bindpose和invgbindpos到相应的地方
//4
//sub mesh
//"SUBMESH"
var materialid=0;
this.mesh.submeshes.forEach((sm,i)=>{
var sminfo=this.submeshInfo[i];
this.wstrid('SUBMESH')
.wstrid('SUBMESH')
.wu8(materialid);
this.wstrid(sm.attribs)
.wu32(sminfo.iboff)//ibofs 都是相对于data的
.wu32(sminfo.ibsize)//ibsize
.wu32(0)//vbIndicesofs 不知道干什么的
.wu32(0)//vbIndicessize 不知道干什么的
.wu32(sminfo.vboff)//vbofs
.wu32(sminfo.vbsize)//vbsize
.wu32(sminfo.boneidxoff)//bonedicofs
.wu32(sminfo.boneidxsize);//bonedicsize
});
//写vb,ib,bonedic:int8[]
//5
//DATAAREA
this.wstrid('DATAAREA');//=6
this.dataOff=this._writePos;
this.strsOff=this._writePos-this.dataOff;
this.strsNum=this._strmap.length;
//strings
this._strmap.forEach((v)=>{
this.wstr(v);
});
//pose
this.bindPoseOff=this._writePos-this.dataOff;
this.bindPoseSize = this.mesh._bindPoses.byteLength;
this.wab(this.mesh._bindPoses.buffer,this.mesh._bindPoses.byteLength);
//invpose
this.invGBindePoseOff=this._writePos-this.dataOff;
this.invGBindePoseSize=this.mesh._inverseBindPoses.byteLength;
this.wab(this.mesh._inverseBindPoses.buffer,this.mesh._inverseBindPoses.byteLength);
this.mesh.submeshes.forEach((sm,i)=>{
var sminfo = this.submeshInfo[i];
//vb
sminfo.vboff=this._writePos-this.dataOff;
sminfo.vbsize=sm._vertexBuffer.byteLength;
this.wab(sm._vertexBuffer.buffer,sm._vertexBuffer.byteLength);
//ib
sminfo.iboff=this._writePos-this.dataOff;
sminfo.ibsize=sm._indexBuffer.byteLength;
this.wab(sm._indexBuffer.buffer,sm._indexBuffer.byteLength);
//bone index
sminfo.boneidxoff=this._writePos-this.dataOff;
sminfo.boneidxsize=sm._boneindicesBuffer.byteLength;
this.wab(sm._boneindicesBuffer.buffer,sm._boneindicesBuffer.byteLength);
});
}
wstrid(str:string):Laya_Mesh_W{//先转成id
var i = this._strmap.indexOf(str);
if(i==-1){
i=this._strmap.length;
this._strmap.push(str);
}
this.wu16(i);
return this;
}
}
/**
* 问题:现在的格式无法略过不认识的块。
*/
export class loader_lh extends BufferReader{
_strings = ['BLOCK', 'DATA', 'STRINGS']; //初始值。read_STRINGS后会被替换
_funcs = [null,this.read_DATA.bind(this),this.read_STRINGS.bind(this)];
blockCount=0;
dataoffset=0;
datasize=0;
_attrReg = new RegExp("(\\w+)|([:,;])", "g");
_shaderAttributes:string[];
mesh:Laya_Mesh;
_cursubmeshid=0;
constructor(){
super();
}
load(str:string,mesh:Laya_Mesh){
this.mesh=mesh;
}
parse(data:ArrayBuffer):any{
this.init(data);
this.mesh.version=this.readString();
this.read_BLOCK();
for( var i=0; i<this.blockCount; i++){
var idx = this.readU16();
var blcokname = this._strings[idx];
var func = this['read_'+blcokname];
if(func){
func.call(this);
}else{
console.error('no function to read chunk '+blcokname);
}
if(idx==2){//strings 特殊
var lmaturls = this._strings.filter((v)=>{ return v.indexOf('.lmat')>0;});
}
}
}
read_BLOCK(){
this.readpos+=2;
this.blockCount = this.readU16();
}
read_DATA(){
this.dataoffset = this.readU32();
this.datasize = this.readU32();
}
read_STRINGS(){
var ret = [];
var offset = this.readU16();
var size = this.readU16();
var oldpos = this.readpos;
//strings的绝对偏移。注意是相对于data块的
this.readpos= this.dataoffset+offset;
for(var i=0; i<size; i++){
var ss = this.readString();
ret.push(ss);
}
this._strings = ret;
this.readpos = oldpos;
}
read_MATERIAL(){
var index = this.readU16();
var stridx = this.readU16();
var urlidx = this.readU16();
var shadername = this._strings[stridx];
var url = this._strings[urlidx];
// mesh
if(index<0||index>255)throw 'material index error:'+index;
this.mesh.materials[index]=url;
}
read_MESH(){
var name = this.getStr(this.readU16());
switch(this.mesh.version){
case 'LAYASKINANI:01':
var bindPoseStart = this.readU32();
var binPoseLength = this.readU32();
var ttt = this.buff.slice(bindPoseStart + this.dataoffset);//由于起始位置不是4的倍数,只能重新复制一个
var bindPoseDatas = new Float32Array(ttt,0, binPoseLength);
var invBindPoseStart = this.readU32();
var invBindPoseLength = this.readU32();
ttt = this.buff.slice(invBindPoseStart+this.dataoffset);
var invBindPoseDatas = new Float32Array(ttt, 0,invBindPoseLength);
//mesh
this.mesh._bindPoses = bindPoseDatas;
this.mesh._inverseBindPoses = invBindPoseDatas;
break;
default:
throw ('wrong version :'+this.mesh.version);
//break;
}
}
read_SUBMESH(){
var className = this.getStr(this.readU16());
var material = this.readU8();//这个没有用
var bufferAttribute = this.getStr(this.readU16());
this._shaderAttributes = bufferAttribute.match(this._attrReg);
var ibofs = this.readU32();
var ibsize = this.readU32();
var vbIndicesofs = this.readU32(); //Not use? 骨骼索引?
var vbIndicessize = this.readU32(); //not use?
var vbofs = this.readU32();
var vbsize = this.readU32();
var boneDicofs = this.readU32();
var boneDicsize = this.readU32();
//vertex decl
//this._getVertexDeclaration();
var vbStart = vbofs+this.dataoffset;
var vbBuff = new Float32Array( this.buff.slice(vbStart,vbStart+vbsize));
//TODO ib 可能不从0开始
//现在是相同格式的合并,但是这样可能会导致错误。
var ibStart = ibofs+this.dataoffset;
var ibBuff = new Uint16Array( this.buff.slice(ibStart, ibStart+ibsize));
//bone index
var bidBuff = new Uint8Array(this.buff.slice(boneDicofs+this.dataoffset,boneDicofs+this.dataoffset+boneDicsize));
//mesh
var sm = this.mesh.submeshes[this._cursubmeshid++]=new Laya_SubMesh();
sm.attribs = bufferAttribute;
sm._vertexBuffer = vbBuff;
sm._indexBuffer=ibBuff;
sm._boneindicesBuffer = bidBuff;
}
read_DATAAREA(){}
getStr(id:number):string{
return this._strings[id];
}
_getVertexDeclaration(){
var position:boolean, normal:boolean, color:boolean, texcoord0:boolean, texcoord1:boolean, tangent:boolean, blendWeight:boolean, blendIndex:boolean;
for (var i = 0; i < this._shaderAttributes.length; i += 8) {
switch (this._shaderAttributes[i]) {
case "POSITION":
position = true;
break;
case "NORMAL":
normal = true;
break;
case "COLOR":
color = true;
break;
case "UV":
texcoord0 = true;
break;
case "UV1":
texcoord1 = true;
break;
case "BLENDWEIGHT":
blendWeight = true;
break;
case "BLENDINDICES":
blendIndex = true;
break;
case "TANGENT":
tangent = true;
break;
}
}
//TODO
this._shaderAttributes;
}
}
var srcPath = 'E:/layaair/layaair/publish/LayaAirPublish/samples/as/3d/bin/h5/threeDimen/models/test/';
class VertexAttrib{
name='';
size=0;
type=0;
offset=0;
}
class VertexDeclare{
attribs:VertexAttrib[]=[];
getTypeLen(t:number):number{
return 4;
}
getStride(){
if(this.attribs.length<=0)
return 0;
var lastattr = this.attribs[this.attribs.length-1];
return lastattr.offset+this.getTypeLen(lastattr.type)*lastattr.size;
}
getOff(name:string):number{
var att = this.attribs.find((v,i)=>{return v.name==name});
return att.offset;
}
}
function parseAttrib(str:string):VertexDeclare{
//0 name, 1 size, 2 stide, 3 offset
var R = new VertexDeclare();
var ret=R.attribs;
var atts = str.split(';');
if(atts.length<=0) throw 'parse att string error'+str;
atts.forEach((v,i)=>{
if(v.length<=0)
return;
var s1 = v.split(':');
if(s1.length<2) throw 'parse att err1';
var ca =new VertexAttrib();
ca.name = s1[0];
var s2 = s1[1].split(',');
if(s2.length<3) throw 'parse att err2';
ca.size=parseInt(s2[0]);
ca.type=parseInt(s2[1]);
ca.offset=parseInt(s2[2]);
ret.push(ca);
});
return R;
}
function handlelm(mesh:Laya_Mesh,modelcnfg:Object,swapyz:boolean):boolean{
if(mesh.version=='LAYAMODEL:02'){
return false;
}
mesh.version = 'LAYAMODEL:02';
mesh.materials.forEach((mtl,i)=>{
if(modelcnfg && modelcnfg[mesh.materials[i]]){
mesh.materials[i]=modelcnfg[mesh.materials[i]];
}
else{
mesh.materials[i]= mesh.materials[i].replace('.lmat','.lpbr');
}
console.log(mesh.materials[i]);
});
mesh.submeshes.forEach((sm,i)=>{
var atts = parseAttrib(sm.attribs);
console.log(atts);
var oldstride = atts.getStride();
var oldfstride=oldstride/4;
if(sm.attribs == 'POSITION:3,32,0;NORMAL:3,32,12;UV:2,32,24;')
sm.attribs='POSITION:3,56,0;NORMAL:3,56,12;TANGENT:3,56,24;BINORMAL:3,56,36;UV:2,56,48;';
if(sm.attribs == 'POSITION:3,64,0;NORMAL:3,64,12;UV:2,64,24;BLENDWEIGHT:4,64,32;BLENDINDICES:4,64,48;')
sm.attribs = 'POSITION:3,88,0;NORMAL:3,88,12;TANGENT:3,88,24;BINORMAL:3,88,36;UV:2,88,48;BLENDWEIGHT:4,88,56;BLENDINDICES:4,88,72;'
//sm.attribs='POSITION:3,32,0;NORMAL:3,32,12;UV:2,32,24;';
var newatts = parseAttrib(sm.attribs);
var newstride = newatts.getStride();
var newfstride=newstride/4;
var oldvb = sm._vertexBuffer;
var vertnum = sm._vertexBuffer.byteLength/oldstride;
var mg = new MeshGeo(oldvb,sm._indexBuffer,oldfstride,atts.getOff('UV')/4,'');
//BBX
var minx=1e6,miny=1e6,minz=1e6,maxx=-1e6,maxy=-1e6,maxz=-1e6;
for( var 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;
}
console.log('submesh size:[',minx,miny,minz,maxx,maxy,maxz,']');
var coldi=0
//缩放
if( modelcnfg && modelcnfg['scale']!=undefined ){
var scale = modelcnfg['scale']*1.0;
for( var 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( var 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
var tb = mg.calcTangent();
var vb = sm._vertexBuffer = new Float32Array(vertnum*newfstride);
var cnewi=0;
for( var 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++];
}
}
});
return true;
}
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_lh();
cc.mesh = new Laya_Mesh();
var data = fs.readFileSync(file);
try{
console.log('parse '+file);
cc.parse(data.buffer);
}catch(e){
console.log(e);
return;
}
var rfile = path.basename(file);
var filepath = path.dirname(file);
//材质相关
cc.mesh.materials.forEach((v,i)=>{
var lmat = filepath+v;
var lpbr = filepath+v.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.mesh,modelCnfgObj[rfile],true)){
var save = new Laya_Mesh_W();
save.mesh=cc.mesh;
save.saveAsLm(file+'.lm');
}else{
}
}
},false);
}
//dowork();