From ab6786e463d448e4f8fd34d35d4eaf17a23c9632 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 19 Jul 2017 13:23:06 -0400 Subject: [PATCH] Remove dependence on gltf-pipeline and added gltfToGlb function --- README.md | 6 -- bin/obj2gltf.js | 39 ------------- doc/cesium.png | Bin 4664 -> 0 bytes lib/createGltf.js | 7 +-- lib/obj2gltf.js | 115 +++++--------------------------------- lib/writeUris.js | 35 +++++++----- package.json | 1 - specs/lib/obj2gltfSpec.js | 114 +++++++++---------------------------- 8 files changed, 63 insertions(+), 254 deletions(-) delete mode 100644 doc/cesium.png diff --git a/README.md b/README.md index c930878..de7e357 100644 --- a/README.md +++ b/README.md @@ -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`| diff --git a/bin/obj2gltf.js b/bin/obj2gltf.js index 1eb8262..4f454bc 100644 --- a/bin/obj2gltf.js +++ b/bin/obj2gltf.js @@ -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, diff --git a/doc/cesium.png b/doc/cesium.png deleted file mode 100644 index 32db61698f08c8d7ef37b002e0ab294df1c34a37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4664 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 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. */ - diff --git a/lib/writeUris.js b/lib/writeUris.js index 1dea8c2..c7e785b 100644 --- a/lib/writeUris.js +++ b/lib/writeUris.js @@ -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]); } } diff --git a/package.json b/package.json index 4111da6..46fa4c4 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/specs/lib/obj2gltfSpec.js b/specs/lib/obj2gltfSpec.js index dfc12bb..1ea5a0e 100644 --- a/specs/lib/obj2gltfSpec.js +++ b/specs/lib/obj2gltfSpec.js @@ -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,