|
const commandLineArgs = require('command-line-args');
|
|
const commandLineUsage = require('command-line-usage');
|
|
|
|
const fs = require('fs-extra');
|
|
const path = require('path');
|
|
|
|
const PNG = require('pngjs').PNG;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const optionDefinitions = [
|
|
{
|
|
name: 'help',
|
|
alias: 'h',
|
|
type: Boolean,
|
|
description: 'Display this usage guide.'
|
|
},
|
|
{
|
|
name: 'skeletonFilePath',
|
|
alias: 's',
|
|
type: String,
|
|
description: '/path/to/accessory-file/skeleton-gltf-file'
|
|
},
|
|
{
|
|
name: 'accessoriesFilePath',
|
|
alias: 'a',
|
|
type: String,
|
|
multiple: true,
|
|
|
|
description: '/path/to/accessory-gltf-file [multiple]'
|
|
},
|
|
{
|
|
name: 'outputFolder',
|
|
alias: 'f',
|
|
type: String,
|
|
defaultValue: './',
|
|
description: '/path/to/outputFolder'
|
|
},
|
|
{
|
|
name: 'outputFilename',
|
|
alias: 'o',
|
|
type: String,
|
|
defaultValue: 'output.gltf',
|
|
description: 'output filename'
|
|
}
|
|
];
|
|
|
|
const options = commandLineArgs(optionDefinitions);
|
|
|
|
if (options.help) {
|
|
const usage = commandLineUsage([
|
|
{
|
|
header: 'glAvatar Merge test',
|
|
content: 'Merge skeleton and accessories gltf into one gltf file'
|
|
},
|
|
{
|
|
header: 'Options',
|
|
optionList: optionDefinitions
|
|
}
|
|
]);
|
|
console.log(usage);
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var skeletonGltfDir = path.dirname(options.skeletonFilePath);
|
|
var accessoryFilepaths = options.accessoriesFilePath;
|
|
|
|
console.log('skeleton filename: ', options.skeletonFilePath);
|
|
console.log('skin filenames: ');
|
|
for (var i = 0, len = accessoryFilepaths.length; i < len; i++) {
|
|
console.log(accessoryFilepaths[i]);
|
|
}
|
|
|
|
|
|
var skeleton = JSON.parse(fs.readFileSync(options.skeletonFilePath));
|
|
if(!skeleton.extensions) {
|
|
skeleton.extensions = {};
|
|
}
|
|
|
|
if (!skeleton.extensions.gl_avatar) {
|
|
skeleton.extensions.gl_avatar = {};
|
|
}
|
|
|
|
|
|
if (!skeleton.extensions.gl_avatar.visibility) {
|
|
skeleton.extensions.gl_avatar.visibility = [];
|
|
}
|
|
|
|
|
|
var textureWithVisibility = null;
|
|
var bodyIdLUTTexture = null;
|
|
|
|
var visiblityMaterial = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function visibilityAndOperation(vi) {
|
|
if (skeleton.extensions.gl_avatar.visibility.length === 0) {
|
|
skeleton.extensions.gl_avatar.visibility = vi.slice(0);
|
|
return;
|
|
}
|
|
|
|
var v = skeleton.extensions.gl_avatar.visibility;
|
|
var vl = v.length;
|
|
for (var i = 0, len = vi.length; i < len; i++) {
|
|
if (vl <= i) {
|
|
v[i] = vi[i];
|
|
} else {
|
|
v[i] = v[i] && vi[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function merge(skeleton, skin) {
|
|
var i, len, j, lenj;
|
|
|
|
var linkedSkeletons = skin.extensions.gl_avatar.linkedSkeletons || [];
|
|
|
|
|
|
|
|
var bufferBaseId = skeleton.buffers.length;
|
|
|
|
|
|
|
|
skeleton.buffers = skeleton.buffers.concat(skin.buffers);
|
|
|
|
|
|
|
|
var bufferViewBaseId = skeleton.bufferViews.length;
|
|
for (i = 0, len = skin.bufferViews.length; i < len; i++) {
|
|
skeleton.bufferViews.push(skin.bufferViews[i]);
|
|
skeleton.bufferViews[i + bufferViewBaseId].buffer += bufferBaseId;
|
|
}
|
|
|
|
|
|
var accessorBaseId = skeleton.accessors.length;
|
|
for (i = 0, len = skin.accessors.length; i < len; i++) {
|
|
skeleton.accessors.push(skin.accessors[i]);
|
|
skeleton.accessors[i + accessorBaseId].bufferView += bufferViewBaseId;
|
|
}
|
|
|
|
|
|
|
|
var imageBaseId = skeleton.images.length;
|
|
skeleton.images = skeleton.images.concat(skin.images);
|
|
|
|
|
|
var samplerBaseId = skeleton.samplers.length;
|
|
skeleton.samplers = skeleton.samplers.concat(skin.samplers);
|
|
|
|
|
|
var textureBaseId = skeleton.textures.length;
|
|
skeleton.textures = skeleton.textures.concat(skin.textures);
|
|
for (i = 0, len = skin.textures.length; i < len; i++) {
|
|
var t = skeleton.textures[i + textureBaseId];
|
|
if (t.sampler !== undefined) {
|
|
t.sampler += samplerBaseId;
|
|
}
|
|
if (t.source !== undefined) {
|
|
t.source += imageBaseId;
|
|
}
|
|
}
|
|
|
|
|
|
var materialBaseId = skeleton.materials.length;
|
|
|
|
|
|
for (i = 0, len = skeleton.materials.length; i < len; i++) {
|
|
var m = skeleton.materials[i];
|
|
if (m.extensions && m.extensions.gl_avatar && m.extensions.gl_avatar.bodyIdLUT !== undefined) {
|
|
|
|
visiblityMaterial = m;
|
|
bodyIdLUTTexture = skeleton.textures[m.extensions.gl_avatar.bodyIdLUT];
|
|
textureWithVisibility = skeleton.textures[m.pbrMetallicRoughness.baseColorTexture.index];
|
|
}
|
|
}
|
|
|
|
|
|
for (i = 0, len = skin.materials.length; i < len; i++) {
|
|
skeleton.materials.push(skin.materials[i]);
|
|
var m = skeleton.materials[i + materialBaseId];
|
|
if (m.pbrMetallicRoughness !== undefined) {
|
|
if (m.pbrMetallicRoughness.baseColorTexture !== undefined) {
|
|
var bt = m.pbrMetallicRoughness.baseColorTexture;
|
|
for (var tt in bt) {
|
|
bt[tt] += imageBaseId;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
var meshBaseId = skeleton.meshes.length;
|
|
for (i = 0, len = skin.meshes.length; i < len; i++) {
|
|
skeleton.meshes.push(skin.meshes[i]);
|
|
var m = skeleton.meshes[i + meshBaseId];
|
|
if (m.primitives !== undefined) {
|
|
|
|
for (j = 0, lenj = m.primitives.length; j < lenj; j++) {
|
|
var p = m.primitives[j];
|
|
|
|
if (p.indices !== undefined) {
|
|
p.indices += accessorBaseId;
|
|
}
|
|
|
|
if (p.material !== undefined) {
|
|
p.material += materialBaseId;
|
|
}
|
|
|
|
if (p.attributes !== undefined) {
|
|
var a = p.attributes;
|
|
for (var att in a) {
|
|
a[att] += accessorBaseId;
|
|
}
|
|
}
|
|
|
|
if (p.extensions !== undefined) {
|
|
if (p.extensions.gl_avatar.attributes) {
|
|
var ea = p.extensions.gl_avatar.attributes;
|
|
if (!p.attributes) {
|
|
p.attributes = {};
|
|
}
|
|
for (var att2 in ea) {
|
|
p.attributes[att2] = ea[att2] + accessorBaseId;
|
|
}
|
|
}
|
|
|
|
delete p.extensions;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
var nodeBaseId = skeleton.nodes.length;
|
|
|
|
for (i = 0, len = skin.nodes.length; i < len; i++) {
|
|
skeleton.nodes.push(skin.nodes[i]);
|
|
var n = skeleton.nodes[i + nodeBaseId];
|
|
if (n.children !== undefined) {
|
|
var c = n.children;
|
|
for (j = 0, lenj = c.length; j < lenj; j++) {
|
|
c[j] += nodeBaseId;
|
|
}
|
|
}
|
|
|
|
if (n.mesh !== undefined) {
|
|
n.mesh += meshBaseId;
|
|
}
|
|
|
|
|
|
if (n.extensions) {
|
|
|
|
|
|
if (n.extensions.gl_avatar && n.extensions.gl_avatar.skin !== undefined) {
|
|
|
|
|
|
var linkedSkinInfo = linkedSkeletons[n.extensions.gl_avatar.skin];
|
|
var skinKey = linkedSkinInfo.skeleton;
|
|
var newSkin = Object.assign({}, skeleton.skins[skeleton.extensions.gl_avatar.skins[skinKey]]);
|
|
skeleton.skins.push(newSkin);
|
|
|
|
n.skin = skeleton.skins.length - 1;
|
|
newSkin.inverseBindMatrices = linkedSkinInfo.inverseBindMatrices + accessorBaseId;
|
|
}
|
|
|
|
delete n.extensions;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
var sceneNodeBaseId = skeleton.scenes[0].nodes.length;
|
|
skeleton.scenes[0].nodes = skeleton.scenes[0].nodes.concat(skin.scenes[0].nodes);
|
|
for (i = 0, len = skin.scenes[0].nodes.length; i < len; i++) {
|
|
skeleton.scenes[0].nodes[i + sceneNodeBaseId] += nodeBaseId;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
visibilityAndOperation(skin.extensions.gl_avatar.visibility);
|
|
|
|
}
|
|
|
|
|
|
function ensureDirectoryExistence(filePath) {
|
|
var dirname = path.dirname(filePath);
|
|
if (fs.existsSync(dirname)) {
|
|
return true;
|
|
}
|
|
ensureDirectoryExistence(dirname);
|
|
fs.mkdirSync(dirname);
|
|
}
|
|
|
|
function copyAssets(inputFolder, outputFolder) {
|
|
|
|
|
|
var assets = fs.readdirSync(inputFolder);
|
|
for (var i = 0, len = assets.length; i < len; i++) {
|
|
if (path.extname(assets[i]) !== '.gltf') {
|
|
fs.copySync( path.join(inputFolder, assets[i]), path.join(outputFolder, assets[i]) );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function bakeVisibility(texPath, bodyIdLUTPath, visibilty) {
|
|
console.log('Texture with visibility path: ', texPath);
|
|
console.log('body Id LUT texture path: ', bodyIdLUTPath);
|
|
|
|
visiblityMaterial.alphaMode = "MASK";
|
|
visiblityMaterial.alphaCutOff = 0.5;
|
|
|
|
fs.createReadStream(texPath)
|
|
.pipe(new PNG({filterType: 4}))
|
|
.on('parsed', function() {
|
|
|
|
var tex = this;
|
|
|
|
fs.createReadStream(bodyIdLUTPath)
|
|
.pipe(new PNG({filterType: 0}))
|
|
.on('parsed', function() {
|
|
var lut = this;
|
|
|
|
|
|
for (var y = 0; y < this.height; y++) {
|
|
for (var x = 0; x < this.width; x++) {
|
|
var idx = (this.width * y + x) << 2;
|
|
|
|
var bodyId = lut.data[idx];
|
|
|
|
if (visibilty[bodyId] === 0) {
|
|
tex.data[idx + 3] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
tex.pack().pipe(fs.createWriteStream(texPath));
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var outputFilename = path.join(options.outputFolder, options.outputFilename);
|
|
ensureDirectoryExistence(outputFilename);
|
|
|
|
copyAssets(skeletonGltfDir, options.outputFolder);
|
|
for (var i = 0, len = accessoryFilepaths.length; i < len; i++) {
|
|
merge(skeleton, JSON.parse(fs.readFileSync(accessoryFilepaths[i])));
|
|
|
|
copyAssets( path.dirname(accessoryFilepaths[i]), options.outputFolder );
|
|
}
|
|
|
|
|
|
|
|
if (textureWithVisibility && bodyIdLUTTexture){
|
|
var textureWithVisibilityPath = path.join(options.outputFolder, skeleton.images[textureWithVisibility.source].uri);
|
|
var textureBodyIdLUTPath = path.join(options.outputFolder, skeleton.images[bodyIdLUTTexture.source].uri);
|
|
bakeVisibility(textureWithVisibilityPath, textureBodyIdLUTPath, skeleton.extensions.gl_avatar.visibility);
|
|
}
|
|
|
|
|
|
fs.writeFileSync(outputFilename, JSON.stringify(skeleton)); |