Remove dependence on gltf-pipeline and added gltfToGlb function

This commit is contained in:
Sean Lilley 2017-07-19 13:23:06 -04:00
parent 608234dcc4
commit ab6786e463
8 changed files with 63 additions and 254 deletions

View File

@ -37,12 +37,6 @@ Using obj2gltf as a command-line tool:
|`-b`, `--binary`|Save as binary glTF.|No, default `false`|
|`-s`, `--separate`|Writes out separate geometry data files, shader files, and textures instead of embedding them in the glTF file.|No, default `false`|
|`-t`, `--separateTextures`|Write out separate textures only.|No, default `false`|
|`-c`, `--compress`|Quantize positions, compress texture coordinates, and oct-encode normals.|No, default `false`|
|`-z`, `--optimize`|Use the optimization stages in the glTF pipeline.|No, default `false`|
|`-n`, `--generateNormals`|Generate normals if they are missing.|No, default `false`|
|`--optimizeForCesium`|Optimize the glTF for Cesium by using the sun as a default light source.|No, default `false`|
|`--ao`|Apply ambient occlusion to the converted model.|No, default `false`|
|`--bypassPipeline`|Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above.|No, default `false`|
|`--checkTransparency`|Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures are considered to be opaque.|No, default `false`|
|`--secure`|Prevent the converter from reading image or mtl files outside of the input obj directory.|No, default `false`|
|`--inputUpAxis`|Up axis of the obj. Choices are 'X', 'Y', and 'Z'.|No, default `Y`|

View File

@ -48,39 +48,6 @@ var argv = yargs
type: 'boolean',
default: defaults.separateTextures
},
compress : {
alias: 'c',
describe: 'Quantize positions, compress texture coordinates, and oct-encode normals.',
type: 'boolean',
default: defaults.compress
},
optimize : {
alias: 'z',
describe: 'Optimize the glTF for size and runtime performance.',
type: 'boolean',
default: defaults.optimize
},
optimizeForCesium : {
describe: 'Optimize the glTF for Cesium by using the sun as a default light source.',
type: 'boolean',
default: defaults.optimizeForCesium
},
generateNormals : {
alias: 'n',
describe: 'Generate normals if they are missing.',
type: 'boolean',
default: defaults.generateNormals
},
ao : {
describe: 'Apply ambient occlusion to the converted model.',
type: 'boolean',
default: defaults.ao
},
bypassPipeline : {
describe: 'Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above.',
type: 'boolean',
default: defaults.bypassPipeline
},
checkTransparency : {
describe: 'Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures are considered to be opaque.',
type: 'boolean',
@ -138,12 +105,6 @@ var options = {
binary : argv.binary,
separate : argv.separate,
separateTextures : argv.separateTextures,
compress : argv.compress,
optimize : argv.optimize,
optimizeForCesium : argv.optimizeForCesium,
generateNormals : argv.generateNormals,
ao : argv.ao,
bypassPipeline : argv.bypassPipeline,
checkTransparency : argv.checkTransparency,
secure : argv.secure,
inputUpAxis : argv.inputUpAxis,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -613,7 +613,7 @@ function convertTraditionalToMetallicRoughness(material) {
material.specularShininess = roughnessFactor;
}
function createMaterialsCommonMaterial(gltf, images, material, hasNormals, options) {
function createMaterialsCommonMaterial(gltf, images, material, hasNormals) {
var materialName = material.name;
var ambientImage = getImage(images, material.ambientTexture);
@ -649,10 +649,9 @@ function createMaterialsCommonMaterial(gltf, images, material, hasNormals, optio
var doubleSided = transparent;
if (!hasNormals && !options.generateNormals) {
if (!hasNormals) {
// Constant technique only factors in ambient and emission sources - set emission to diffuse
emission = diffuse;
diffuse = [0, 0, 0, 1];
}
var technique = hasNormals ? (hasSpecular ? 'PHONG' : 'LAMBERT') : 'CONSTANT';
@ -689,7 +688,7 @@ function addMaterial(gltf, images, material, hasNormals, options) {
} else if (options.metallicRoughness) {
gltfMaterial = createMetallicRoughnessMaterial(gltf, images, material, options);
} else if (options.materialsCommon) {
gltfMaterial = createMaterialsCommonMaterial(gltf, images, material, hasNormals, options);
gltfMaterial = createMaterialsCommonMaterial(gltf, images, material, hasNormals);
} else {
convertTraditionalToMetallicRoughness(material);
gltfMaterial = createMetallicRoughnessMaterial(gltf, images, material, options);

View File

@ -1,12 +1,10 @@
'use strict';
var Cesium = require('cesium');
var fsExtra = require('fs-extra');
var GltfPipeline = require('gltf-pipeline').Pipeline;
var os = require('os');
var path = require('path');
var Promise = require('bluebird');
var uuid = require('uuid');
var createGltf = require('./createGltf');
var gltfToGlb = require('./gltfToGlb');
var loadObj = require('./loadObj');
var writeUris = require('./writeUris');
@ -26,13 +24,6 @@ module.exports = obj2gltf;
* @param {Boolean} [options.binary=false] Save as binary glTF.
* @param {Boolean} [options.separate=false] Writes out separate geometry data files, shader files, and textures instead of embedding them in the glTF.
* @param {Boolean} [options.separateTextures=false] Write out separate textures only.
* @param {Boolean} [options.compress=false] Quantize positions, compress texture coordinates, and oct-encode normals.
* @param {Boolean} [options.optimize=false] Optimize the glTF for size and runtime performance.
* @param {Boolean} [options.optimizeForCesium=false] Optimize the glTF for Cesium by using the sun as a default light source.
* @param {Boolean} [options.generateNormals=false] Generate normals if they are missing.
* @param {Boolean} [options.ao=false] Apply ambient occlusion to the converted model.
* @param {Boolean} [options.textureCompressionOptions] Options sent to the compressTextures stage of gltf-pipeline.
* @param {Boolean} [options.bypassPipeline=false] Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above.
* @param {Boolean} [options.checkTransparency=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel.
* @param {Boolean} [options.secure=false] Prevent the converter from reading image or mtl files outside of the input obj directory.
* @param {String} [options.inputUpAxis='Y'] Up axis of the obj. Choices are 'X', 'Y', and 'Z'.
@ -42,6 +33,7 @@ module.exports = obj2gltf;
* @param {Boolean} [options.specularGlossiness=false] The values in the mtl file are already specular-glossiness PBR values and no conversion step should be applied. Specular is stored in the Ks and map_Ks slots and glossiness is stored in the Ns and map_Ns slots. The glTF will be saved with the KHR_materials_pbrSpecularGlossiness extension.
* @param {Boolean} [options.materialsCommon=false] The glTF will be saved with the KHR_materials_common extension.
* @param {Logger} [options.logger] A callback function for handling logged messages. Defaults to console.log.
* @return {Promise} A promise that resolves when the glTF file is saved.
*/
function obj2gltf(objPath, gltfPath, options) {
var defaults = obj2gltf.defaults;
@ -50,13 +42,6 @@ function obj2gltf(objPath, gltfPath, options) {
var binary = defaultValue(options.binary, defaults.binary);
var separate = defaultValue(options.separate, defaults.separate);
var separateTextures = defaultValue(options.separateTextures, defaults.separateTextures) || separate;
var compress = defaultValue(options.compress, defaults.compress);
var optimize = defaultValue(options.optimize, defaults.optimize);
var optimizeForCesium = defaultValue(options.optimizeForCesium, defaults.optimizeForCesium);
var generateNormals = defaultValue(options.generateNormals, defaults.generateNormals);
var ao = defaultValue(options.ao, defaults.ao);
var textureCompressionOptions = options.textureCompressionOptions;
var bypassPipeline = defaultValue(options.bypassPipeline, defaults.bypassPipeline);
var checkTransparency = defaultValue(options.checkTransparency, defaults.checkTransparency);
var secure = defaultValue(options.secure, defaults.secure);
var inputUpAxis = defaultValue(options.inputUpAxis, defaults.inputUpAxis);
@ -67,7 +52,6 @@ function obj2gltf(objPath, gltfPath, options) {
var materialsCommon = defaultValue(options.materialsCommon, defaults.materialsCommon);
var logger = defaultValue(options.logger, defaults.logger);
options.generateNormals = generateNormals;
options.separate = separate;
options.separateTextures = separateTextures;
options.checkTransparency = checkTransparency;
@ -93,34 +77,18 @@ function obj2gltf(objPath, gltfPath, options) {
if (extension === '.glb') {
binary = true;
}
if (binary && bypassPipeline) {
return Promise.reject(new RuntimeError('--bypassPipeline does not convert to binary glTF'));
if (binary) {
extension = '.glb';
}
gltfPath = path.join(path.dirname(gltfPath), modelName + extension);
if (metallicRoughness + specularGlossiness + materialsCommon > 1) {
return Promise.reject(new RuntimeError('Only one material type may be set from [--metallicRoughness, --specularGlossiness, --materialsCommon].'));
}
gltfPath = path.join(path.dirname(gltfPath), modelName + extension);
var resourcesDirectory = options.bypassPipeline ? path.dirname(gltfPath) : obj2gltf._getTempDirectory();
var aoOptions = ao ? {} : undefined;
var pipelineOptions = {
createDirectory : false,
basePath : resourcesDirectory,
binary : binary,
embed : !separate,
embedImage : !separateTextures,
quantize : compress,
compressTextureCoordinates : compress,
encodeNormals : compress,
preserve : !optimize,
optimizeForCesium : optimizeForCesium,
smoothNormals : generateNormals,
aoOptions : aoOptions,
textureCompressionOptions : textureCompressionOptions
var jsonOptions = {
spaces : 2
};
return loadObj(objPath, options)
@ -128,28 +96,17 @@ function obj2gltf(objPath, gltfPath, options) {
return createGltf(objData, options);
})
.then(function(gltf) {
return writeUris(gltf, gltfPath, resourcesDirectory, options);
return writeUris(gltf, gltfPath, options);
})
.then(function(gltf) {
if (bypassPipeline) {
return fsExtra.outputJson(gltfPath, gltf);
if (binary) {
var glb = gltfToGlb(gltf);
return fsExtra.outputFile(gltfPath, glb);
}
return GltfPipeline.processJSONToDisk(gltf, gltfPath, pipelineOptions);
})
.finally(function() {
return cleanup(resourcesDirectory, options);
return fsExtra.outputJson(gltfPath, gltf, jsonOptions);
});
}
function cleanup(resourcesDirectory, options) {
if (!options.bypassPipeline && options.separate) {
fsExtra.remove(resourcesDirectory, function () {
// Don't fail simply because we couldn't
// clean up the temporary files.
});
}
}
/**
* Default values that will be used when calling obj2gltf(options) unless specified in the options object.
*/
@ -173,42 +130,6 @@ obj2gltf.defaults = {
* @default false
*/
separateTextures: false,
/**
* Gets or sets whether to compress attribute data. This includes quantizing positions, compressing texture coordinates, and oct-encoding normals.
* @type Boolean
* @default false
*/
compress: false,
/**
* Gets or sets whether the model is optimized for size and runtime performance.
* @type Boolean
* @default false
*/
optimize: false,
/**
* Gets or sets whether the model is optimized for Cesium by using the sun as a default light source.
* @type Boolean
* @default false
*/
optimizeForCesium: false,
/**
* Gets or sets whether normals will be generated for the model if they are missing.
* @type Boolean
* @default false
*/
generateNormals: false,
/**
* Gets or sets whether the model will have ambient occlusion applied.
* @type Boolean
* @default false
*/
ao: false,
/**
* Gets or sets whether the converter will bypass the gltf-pipeline for debugging purposes.
* @type Boolean
* @default false
*/
bypassPipeline: false,
/**
* Gets or sets whether the converter will do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel.
* @type Boolean
@ -266,19 +187,9 @@ obj2gltf.defaults = {
}
};
/**
* Exposed for testing
*
* @private
*/
obj2gltf._getTempDirectory = function () {
return path.join(os.tmpdir(), uuid());
};
/**
* A callback function that logs messages.
* @callback Logger
*
* @param {String} message The message to log.
*/

View File

@ -15,7 +15,6 @@ module.exports = writeUris;
*
* @param {Object} gltf The glTF asset.
* @param {String} gltfPath Path where the glTF will be saved.
* @param {String} resourcesDirectory Path where separate resources will be saved.
* @param {Object} options An object with the following properties:
* @param {Boolean} options.separate Writes out separate buffers.
* @param {Boolean} options.separateTextures Write out separate textures only.
@ -23,7 +22,7 @@ module.exports = writeUris;
*
* @private
*/
function writeUris(gltf, gltfPath, resourcesDirectory, options) {
function writeUris(gltf, gltfPath, options) {
var separate = options.separate;
var separateTextures = options.separateTextures;
@ -48,18 +47,18 @@ function writeUris(gltf, gltfPath, resourcesDirectory, options) {
var name = path.basename(gltfPath, path.extname(gltfPath));
if (separate) {
promises.push(writeSeparateBuffer(gltf, resourcesDirectory, name));
} else {
writeEmbeddedBuffer(gltf);
}
if (separateTextures) {
promises.push(writeSeparateTextures(gltf, resourcesDirectory));
promises.push(writeSeparateTextures(gltf, gltfPath));
} else {
writeEmbeddedTextures(gltf);
}
if (separate) {
promises.push(writeSeparateBuffer(gltf, gltfPath, name));
} else {
writeEmbeddedBuffer(gltf);
}
return Promise.all(promises)
.then(function() {
deleteExtras(gltf);
@ -99,22 +98,22 @@ function cleanup(gltf) {
removeEmpty(gltf);
}
function writeSeparateBuffer(gltf, resourcesDirectory, name) {
function writeSeparateBuffer(gltf, gltfPath, name) {
var buffer = gltf.buffers[0];
var source = buffer.extras._obj2gltf.source;
var bufferUri = name + '.bin';
buffer.uri = bufferUri;
var bufferPath = path.join(resourcesDirectory, bufferUri);
var bufferPath = path.join(path.dirname(gltfPath), bufferUri);
return fsExtra.outputFile(bufferPath, source);
}
function writeSeparateTextures(gltf, resourcesDirectory) {
function writeSeparateTextures(gltf, gltfPath) {
var images = gltf.images;
return Promise.map(images, function(image) {
var extras = image.extras._obj2gltf;
var imageUri = image.name + extras.extension;
image.uri = imageUri;
var imagePath = path.join(resourcesDirectory, imageUri);
var imagePath = path.join(path.dirname(gltfPath), imageUri);
return fsExtra.outputFile(imagePath, extras.source);
}, {concurrency : 10});
}
@ -126,11 +125,19 @@ function writeEmbeddedBuffer(gltf) {
}
function writeEmbeddedTextures(gltf) {
var bufferSource = gltf.buffers[0].extras._obj2gltf.source;
var images = gltf.images;
var imagesLength = images.length;
for (var i = 0; i < imagesLength; ++i) {
var image = images[i];
var extras = image.extras._obj2gltf;
image.uri = 'data:' + mime.lookup(extras.extension) + ';base64,' + extras.source.toString('base64');
var imageSource = extras.source;
image.mimeType = mime.lookup(extras.extension);
gltf.bufferViews.push({
buffer : 0,
byteOffset : bufferSource.length,
byteLength : imageSource.byteLength
});
bufferSource = Buffer.concat([bufferSource, imageSource]);
}
}

View File

@ -29,7 +29,6 @@
"bluebird": "^3.5.0",
"cesium": "^1.35.2",
"fs-extra": "^4.0.0",
"gltf-pipeline": "^1.0.0",
"jpeg-js": "^0.3.3",
"mime": "^1.3.6",
"pngjs": "^3.2.0",

View File

@ -1,8 +1,6 @@
'use strict';
var Cesium = require('Cesium');
var fsExtra = require('fs-extra');
var GltfPipeline = require('gltf-pipeline').Pipeline;
var os = require('os');
var path = require('path');
var Promise = require('bluebird');
var obj2gltf = require('../../lib/obj2gltf');
@ -15,111 +13,59 @@ var glbPath = 'specs/data/box-textured/box-textured.glb';
var objPathNonExistent = 'specs/data/non-existent.obj';
describe('obj2gltf', function() {
var tempDirectory;
beforeAll(function() {
expect(obj2gltf._getTempDirectory()).toContain(os.tmpdir());
tempDirectory = path.join(os.tmpdir(), 'testPath');
spyOn(obj2gltf, '_getTempDirectory').and.returnValue(tempDirectory);
spyOn(fsExtra, 'outputJson');
spyOn(fsExtra, 'outputFile');
spyOn(fsExtra, 'remove');
});
beforeEach(function() {
spyOn(GltfPipeline, 'processJSONToDisk').and.returnValue(Promise.resolve());
spyOn(fsExtra, 'outputJson').and.returnValue(Promise.resolve());
spyOn(fsExtra, 'outputFile').and.returnValue(Promise.resolve());
});
it('converts an obj to gltf', function(done) {
it('converts obj to gltf', function(done) {
expect(obj2gltf(objPath, gltfPath)
.then(function() {
var args = GltfPipeline.processJSONToDisk.calls.first().args;
var gltf = args[0];
var outputPath = args[1];
var options = args[2];
var args = fsExtra.outputJson.calls.first().args;
var outputPath = args[0];
var gltf = args[1];
expect(path.normalize(outputPath)).toEqual(path.normalize(gltfPath));
expect(gltf).toBeDefined();
expect(gltf.images.length).toBe(1);
expect(options).toEqual({
basePath : tempDirectory,
createDirectory : false,
binary : false,
embed : true,
embedImage : true,
encodeNormals : false,
quantize : false,
compressTextureCoordinates : false,
aoOptions : undefined,
smoothNormals : false,
optimizeForCesium : false,
textureCompressionOptions : undefined,
preserve : true
});
}), done).toResolve();
});
it('sets options', function(done) {
var textureCompressionOptions = {
format : 'dxt1',
quality : 10
};
it('converts obj to glb', function(done) {
var options = {
binary : true,
separate : true,
separateTextures : true,
compress : true,
optimize : true,
optimizeForCesium : true,
generateNormals : true,
ao : true,
textureCompressionOptions : textureCompressionOptions,
checkTransparency : true,
secure : true,
inputUpAxis : 'Z',
outputUpAxis : 'X',
logger : obj2gltf.defaults.logger
binary : true
};
expect(obj2gltf(objPath, gltfPath, options)
.then(function() {
var args = GltfPipeline.processJSONToDisk.calls.first().args;
var options = args[2];
expect(options).toEqual({
basePath : tempDirectory,
createDirectory : false,
binary : true,
embed : false,
embedImage : false,
encodeNormals : true,
quantize : true,
compressTextureCoordinates : true,
aoOptions : {},
smoothNormals : true,
optimizeForCesium : true,
textureCompressionOptions : textureCompressionOptions,
preserve : false
});
expect(fsExtra.outputFile.calls.count()).toBe(2); // Saves out .png and .bin
var args = fsExtra.outputFile.calls.first().args;
var outputPath = args[0];
var glb = args[1];
expect(path.extname(outputPath)).toBe('.glb');
var magic = glb.toString('utf8', 0, 4);
expect(magic).toBe('glTF');
}), done).toResolve();
});
it('saves as binary if gltfPath has a .glb extension', function(done) {
it('converts obj to glb when gltfPath has a .glb extension', function(done) {
expect(obj2gltf(objPath, glbPath)
.then(function() {
var args = GltfPipeline.processJSONToDisk.calls.first().args;
var options = args[2];
expect(options.binary).toBe(true);
var args = fsExtra.outputFile.calls.first().args;
var outputPath = args[0];
var glb = args[1];
expect(path.extname(outputPath)).toBe('.glb');
var magic = glb.toString('utf8', 0, 4);
expect(magic).toBe('glTF');
}), done).toResolve();
});
it('bypassPipeline flag bypasses gltf-pipeline', function(done) {
it('writes out separate resources', function(done) {
var options = {
bypassPipeline : true
separate : true,
separateTextures : true
};
expect(obj2gltf(objPath, gltfPath, options)
.then(function() {
expect(fsExtra.outputJson).toHaveBeenCalled();
expect(GltfPipeline.processJSONToDisk).not.toHaveBeenCalled();
expect(fsExtra.outputFile.calls.count()).toBe(2); // Saves out .png and .bin
expect(fsExtra.outputJson.calls.count()).toBe(1); // Saves out .gltf
}), done).toResolve();
});
@ -139,14 +85,6 @@ describe('obj2gltf', function() {
}).toThrowDeveloperError();
});
it('rejects if both bpypassPipeline and binary are true', function(done) {
var options = {
bypassPipeline : true,
binary : true
};
expect(obj2gltf(objPath, gltfPath, options), done).toRejectWith(RuntimeError);
});
it('rejects if more than one material type is set', function(done) {
var options = {
metallicRoughness : true,