pbrtools
Version:
laya pbr tools
628 lines (627 loc) • 23 kB
JavaScript
"use strict";
const meshGeo_1 = require("./meshGeo");
const buffer_1 = require("./buffer");
const util_1 = require("./util");
const fs = require("fs");
const path = require("path");
class Laya_SubMesh {
constructor() {
this.attribs = 'POSITION:3,32,0;NORMAL:3,32,12;UV:2,32,24;';
}
}
class Laya_Mesh {
constructor() {
this.version = 'LAYASKINANI:01';
this.name = 'MESH';
this.materials = []; //只用字符串表示对应的材质文件或者材质id。有几个就表示有几个submesh
this.submeshes = [];
}
}
class _submeshInfo {
constructor() {
this.iboff = 0;
this.ibsize = 0;
this.vboff = 0;
this.vbsize = 0;
this.boneidxoff = 0;
this.boneidxsize = 0;
}
}
class Laya_Mesh_W extends buffer_1.BufferWriter {
constructor() {
super();
this._strmap = ['BLOCK', 'DATA', 'STRINGS'];
this.dataOff = 0;
this.dataSize = 0;
this.strsOff = 0;
this.strsNum = 0; //
this.bindPoseOff = 0;
this.bindPoseSize = 0;
this.invGBindePoseOff = 0;
this.invGBindePoseSize = 0;
this.submeshInfo = [];
}
/**
* 返回写了多大。
*/
outobj(obj, members, buff, off) {
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) {
}
saveAsLm(file) {
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) {
var i = this._strmap.indexOf(str);
if (i == -1) {
i = this._strmap.length;
this._strmap.push(str);
}
this.wu16(i);
return this;
}
}
exports.Laya_Mesh_W = Laya_Mesh_W;
/**
* 问题:现在的格式无法略过不认识的块。
*/
class loader_lh extends buffer_1.BufferReader {
constructor() {
super();
this._strings = ['BLOCK', 'DATA', 'STRINGS']; //初始值。read_STRINGS后会被替换
this._funcs = [null, this.read_DATA.bind(this), this.read_STRINGS.bind(this)];
this.blockCount = 0;
this.dataoffset = 0;
this.datasize = 0;
this._attrReg = new RegExp("(\\w+)|([:,;])", "g");
this._cursubmeshid = 0;
}
load(str, mesh) {
this.mesh = mesh;
}
parse(data) {
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) {
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);
}
}
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) {
return this._strings[id];
}
_getVertexDeclaration() {
var position, normal, color, texcoord0, texcoord1, tangent, blendWeight, blendIndex;
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;
}
}
exports.loader_lh = loader_lh;
var srcPath = 'E:/layaair/layaair/publish/LayaAirPublish/samples/as/3d/bin/h5/threeDimen/models/test/';
class VertexAttrib {
constructor() {
this.name = '';
this.size = 0;
this.type = 0;
this.offset = 0;
}
}
class VertexDeclare {
constructor() {
this.attribs = [];
}
getTypeLen(t) {
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) {
var att = this.attribs.find((v, i) => { return v.name == name; });
return att.offset;
}
}
function parseAttrib(str) {
//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, modelcnfg, swapyz) {
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_1.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');
}
util_1.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'));
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();