pbrtools
Version:
laya pbr tools
614 lines (613 loc) • 22.5 kB
JavaScript
"use strict";
const buffer_1 = require("./buffer");
const fs = require("fs");
const path = require("path");
const util_1 = require("./util");
const meshGeo_1 = require("./meshGeo");
//Version 2
class data_block2 {
constructor(off, len) {
this.offofData = 0; //文档说是相对内容区,实际是相对于整个文件
this.length = 0;
this.offofData = off;
this.length = len;
}
}
class Mtl_block2 {
constructor() {
this.clsName = '';
this.shaderName = '';
this.mtlPath = '';
}
}
class BindBoneInfo2 {
constructor() {
this._bones = [];
}
}
class MeshInfo2 {
constructor() {
this._name = '';
this._vbInfos = [];
}
}
class SubmeshInfo2 {
constructor() {
this.vbIdx = 0; //short
this.vbstart = 0; //int32
this.vblen = 0; //int32
this.ibstart = 0; //int32;
this.iblen = 0; //int32
this.drawCnt = 0; //short
this.subs = [];
}
}
class VertexAttrib {
constructor(attrib) {
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) {
return this.attribs.indexOf(attr) >= 0;
}
getOff(attr) {
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;
}
}
VertexAttrib.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 }
};
class loader_lh2 extends buffer_1.BufferReader {
constructor() {
super();
this._dataoff = 0; //数据区偏移
this._strings = [];
this._datablocks = [];
this._mtls = [];
this._meshInfo = new MeshInfo2();
this._bindInfo = new BindBoneInfo2();
this._subMeshes = [];
this._attrReg = new RegExp("(\\w+)|([:,;])", "g");
this._cursubmeshid = 0;
}
load(str) {
var data = fs.readFileSync(str);
this.parse(data.buffer);
}
parse(data) {
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: null, _attrib: '' };
let vb = 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) {
return this._strings[id];
}
getOrAddString(str) {
let r = this._strings.indexOf(str);
if (r < 0) {
r = this._strings.length;
this._strings.push(str);
}
return r;
}
save(file) {
//先清空字符串
this._strings = [];
this._mtls.length = 0;
let blockcnt = this._mtls.length +
1 +
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 buffer_1.BufferWriter(headlen);
//头先不写,最后在写
let datapos = 0; //只是数据区偏移,不是整体
//写材质区
let mtlblockw = new buffer_1.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 +
2 +
2 + this._meshInfo._vbInfos.length * (4 + 4 + 2) +
4 +
4 +
2 +
2 * this._bindInfo._bones.length +
4 +
4 +
4 +
4; //len
let meshblockw = new buffer_1.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 buffer_1.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 +
2 +
4 +
4 +
4 +
4 +
2 +
v.drawCnt * 16;
submeshsz += cursz;
this._datablocks.push(new data_block2(datapos + this._dataoff, cursz));
datapos += cursz; //完成后指向了submesh的最后部分
});
let submeshblockw = new buffer_1.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 buffer_1.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 buffer_1.BufferWriter(0);
strsw.nowrite = true; //先计算大小
this._strings.forEach((str) => {
strsw.wstr(str);
});
//然后真写
let strbuffsz = strsw._writePos;
strsw = new buffer_1.BufferWriter(strbuffsz);
this._strings.forEach((str) => {
strsw.wstr(str);
});
//整合
let alldatalen = 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 buffer_1.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, modelcnfg, swapyz) {
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_1.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');
}
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_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'));
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();