diff --git a/README.md b/README.md index de7e357..4427484 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,12 @@ Using obj2gltf as a command-line tool: |`--metallicRoughness`|The values in the mtl file are already metallic-roughness PBR values and no conversion step should be applied. Metallic is stored in the Ks and map_Ks slots and roughness is stored in the Ns and map_Ns slots.|No, default `false`| |`--specularGlossiness`|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.|No, default `false`| |`--materialsCommon`|The glTF will be saved with the KHR_materials_common extension.|No, default `false`| +|`--metallicRoughnessOcclusionTexture`|Path to the metallic-roughness-occlusion texture used by the model, where occlusion is stored in the red channel, roughness is stored in the green channel, and metallic is stored in the blue channel. This may be used instead of setting texture paths in the .mtl file. The model will be saved with a pbrMetallicRoughness material. +|`--specularGlossinessTexture`|Path to the specular-glossiness texture used by the model, where specular color is stored in the red, green, and blue channels and specular glossiness is stored in the alpha channel. This may be used instead of setting texture paths in the .mtl file. The model will be saved with a material using the KHR_materials_pbrSpecularGlossiness extension. +|`--occlusionTexture`|Path to the occlusion texture used by the model. This may be used instead of setting texture paths in the .mtl file. Ignored if metallicRoughnessOcclusionTexture is also set. +|`--normalTexture`|Path to the normal texture used by the model. This may be used instead of setting texture paths in the .mtl file. +|`--baseColorTexture`|Path to the baseColor/diffuse texture used by the model. This may be used instead of setting texture paths in the .mtl file. +|`--emissiveTexture`|Path to the emissive texture used by the model. This may be used instead of setting texture paths in the .mtl file. ## Build Instructions diff --git a/bin/obj2gltf.js b/bin/obj2gltf.js index a2dc0dd..2011bff 100644 --- a/bin/obj2gltf.js +++ b/bin/obj2gltf.js @@ -89,6 +89,36 @@ var argv = yargs describe: 'The glTF will be saved with the KHR_materials_common extension.', type: 'boolean', default: defaults.materialsCommon + }, + metallicRoughnessOcclusionTexture : { + describe: 'Path to the metallic-roughness-occlusion texture used by the model, where occlusion is stored in the red channel, roughness is stored in the green channel, and metallic is stored in the blue channel. This may be used instead of setting texture paths in the .mtl file. The model will be saved with a pbrMetallicRoughness material.', + type: 'string', + normalize: true + }, + specularGlossinessTexture : { + describe: 'Path to the specular-glossiness texture used by the model, where specular color is stored in the red, green, and blue channels and specular glossiness is stored in the alpha channel. This may be used instead of setting texture paths in the .mtl file. The model will be saved with a material using the KHR_materials_pbrSpecularGlossiness extension.', + type: 'string', + normalize: true + }, + occlusionTexture : { + describe: 'Path to the occlusion texture used by the model. This may be used instead of setting texture paths in the .mtl file. Ignored if metallicRoughnessOcclusionTexture is also set.', + type: 'string', + normalize: true + }, + normalTexture : { + describe: 'Path to the normal texture used by the model. This may be used instead of setting texture paths in the .mtl file.', + type: 'string', + normalize: true + }, + baseColorTexture : { + describe: 'Path to the baseColor/diffuse texture used by the model. This may be used instead of setting texture paths in the .mtl file.', + type: 'string', + normalize: true + }, + emissiveTexture : { + describe: 'Path to the emissive texture used by the model. This may be used instead of setting texture paths in the .mtl file.', + type: 'string', + normalize: true } }).parse(args); diff --git a/lib/createGltf.js b/lib/createGltf.js index 59777f2..7c20b35 100644 --- a/lib/createGltf.js +++ b/lib/createGltf.js @@ -21,6 +21,12 @@ module.exports = createGltf; * @param {Boolean} options.metallicRoughness The values in the mtl file are already metallic-roughness PBR values and no conversion step should be applied. Metallic is stored in the Ks and map_Ks slots and roughness is stored in the Ns and map_Ns slots. * @param {Boolean} options.specularGlossiness 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 The glTF will be saved with the KHR_materials_common extension. + * @param {String} [options.metallicRoughnessOcclusionTexture] Path to the metallic-roughness-occlusion texture used by the model, where occlusion is stored in the red channel, roughness is stored in the green channel, and metallic is stored in the blue channel. This may be used instead of setting texture paths in the .mtl file. The model will be saved with a pbrMetallicRoughness material. + * @param {String} [options.specularGlossinessTexture] Path to the specular-glossiness texture used by the model, where specular color is stored in the red, green, and blue channels and specular glossiness is stored in the alpha channel. This may be used instead of setting texture paths in the .mtl file. The model will be saved with a material using the KHR_materials_pbrSpecularGlossiness extension. + * @param {String} [options.occlusionTexture] Path to the occlusion texture used by the model. This may be used instead of setting texture paths in the .mtl file. Ignored if metallicRoughnessOcclusionTexture is also set. + * @param {String} [options.normalTexture] Path to the normal texture used by the model. This may be used instead of setting texture paths in the .mtl file. + * @param {String} [options.baseColorTexture] Path to the baseColor/diffuse texture used by the model. This may be used instead of setting texture paths in the .mtl file. + * @param {String} [options.emissiveTexture] Path to the emissive texture used by the model. This may be used instead of setting texture paths in the .mtl file. * @param {Boolean} options.logger A callback function for handling logged messages. Defaults to console.log. * @returns {Object} A glTF asset. * @@ -152,7 +158,9 @@ function addBuffers(gltf, bufferState) { }); } -function getImage(images, imagePath) { +function getImage(images, imagePath, overrideImagePath, options) { + images = options.overridingImages.concat(images); + imagePath = defaultValue(overrideImagePath, imagePath); var imagesLength = images.length; for (var i = 0; i < imagesLength; ++i) { var image = images[i]; @@ -330,7 +338,7 @@ function encodePng(pixels, width, height, inputChannels, outputChannels) { return PNG.sync.write(pngInput, pngOptions); } -function createMetallicRoughnessTexture(gltf, materialName, metallicImage, roughnessImage, occlusionImage, options) { +function createMetallicRoughnessTexture(gltf, metallicImage, roughnessImage, occlusionImage, options) { var packMetallic = defined(metallicImage); var packRoughness = defined(roughnessImage); var packOcclusion = defined(occlusionImage) && options.packOcclusion; @@ -382,10 +390,13 @@ function createMetallicRoughnessTexture(gltf, materialName, metallicImage, rough writeChannel(pixels, occlusionChannel, 0, width, height); } - var imageName = materialName + '-' + 'MetallicRoughness'; - if (packOcclusion) { - imageName += 'Occlusion'; + var length = packedImages.length; + var imageNames = new Array(length); + for (var i = 0; i < length; ++i) { + var imagePath = packedImages[i].path; + imageNames[i] = path.basename(imagePath, path.extname(imagePath)); } + var imageName = imageNames.join('_'); var pngSource = encodePng(pixels, width, height, 4, 3); @@ -399,7 +410,7 @@ function createMetallicRoughnessTexture(gltf, materialName, metallicImage, rough return addTexture(gltf, image); } -function createSpecularGlossinessTexture(gltf, materialName, specularImage, glossinessImage, options) { +function createSpecularGlossinessTexture(gltf, specularImage, glossinessImage, options) { var packSpecular = defined(specularImage); var packGlossiness = defined(glossinessImage); @@ -443,7 +454,13 @@ function createSpecularGlossinessTexture(gltf, materialName, specularImage, glos writeChannel(pixels, glossinessChannel, 3, width, height); } - var imageName = materialName + '-' + 'SpecularGlossiness'; + var length = packedImages.length; + var imageNames = new Array(length); + for (var i = 0; i < length; ++i) { + var imagePath = packedImages[i].path; + imageNames[i] = path.basename(imagePath, path.extname(imagePath)); + } + var imageName = imageNames.join('_'); var pngSource = encodePng(pixels, width, height, 4, 4); @@ -460,18 +477,25 @@ function createSpecularGlossinessTexture(gltf, materialName, specularImage, glos function createSpecularGlossinessMaterial(gltf, images, material, options) { var materialName = material.name; - var emissiveImage = getImage(images, material.emissiveTexture); - var normalImage = getImage(images, material.normalTexture); - var occlusionImage = getImage(images, material.ambientTexture); - var diffuseImage = getImage(images, material.diffuseTexture); - var specularImage = getImage(images, material.specularTexture); - var glossinessImage = getImage(images, material.specularShininessTexture); + // The texture paths supplied in the .mtl may be overriden by the texture paths supplied in options + var emissiveImage = getImage(images, material.emissiveTexture, options.emissiveTexture, options); + var normalImage = getImage(images, material.normalTexture, options.normalTexture, options); + var occlusionImage = getImage(images, material.ambientTexture, options.occlusionTexture, options); + var diffuseImage = getImage(images, material.diffuseTexture, options.baseColorTexture, options); + var specularImage = getImage(images, material.specularTexture, options.specularGlossinessTexture, options); + var glossinessImage = getImage(images, material.specularShininessTexture, options.specularGlossinessTexture, options); var emissiveTexture = getTexture(gltf, emissiveImage); var normalTexture = getTexture(gltf, normalImage); var occlusionTexture = getTexture(gltf, occlusionImage); var diffuseTexture = getTexture(gltf, diffuseImage); - var specularGlossinessTexture = createSpecularGlossinessTexture(gltf, materialName, specularImage, glossinessImage, options); + + var specularGlossinessTexture; + if (defined(options.specularGlossinessTexture)) { + specularGlossinessTexture = getTexture(gltf, specularImage); + } else { + specularGlossinessTexture = createSpecularGlossinessTexture(gltf, specularImage, glossinessImage, options); + } var emissiveFactor = getEmissiveFactor(material); var diffuseFactor = material.diffuseColor; @@ -531,19 +555,26 @@ function createSpecularGlossinessMaterial(gltf, images, material, options) { function createMetallicRoughnessMaterial(gltf, images, material, options) { var materialName = material.name; - var emissiveImage = getImage(images, material.emissiveTexture); - var normalImage = getImage(images, material.normalTexture); - var occlusionImage = getImage(images, material.ambientTexture); - var baseColorImage = getImage(images, material.diffuseTexture); - var metallicImage = getImage(images, material.specularTexture); - var roughnessImage = getImage(images, material.specularShininessTexture); + // The texture paths supplied in the .mtl may be overriden by the texture paths supplied in options + var emissiveImage = getImage(images, material.emissiveTexture, options.emissiveTexture, options); + var normalImage = getImage(images, material.normalTexture, options.normalTexture, options); + var occlusionImage = getImage(images, material.ambientTexture, options.metallicRoughnessOcclusionTexture, options); + var baseColorImage = getImage(images, material.diffuseTexture, options.baseColorTexture, options); + var metallicImage = getImage(images, material.specularTexture, options.metallicRoughnessOcclusionTexture, options); + var roughnessImage = getImage(images, material.specularShininessTexture, options.metallicRoughnessOcclusionTexture, options); var emissiveTexture = getTexture(gltf, emissiveImage); var normalTexture = getTexture(gltf, normalImage); var baseColorTexture = getTexture(gltf, baseColorImage); - var metallicRoughnessTexture = createMetallicRoughnessTexture(gltf, materialName, metallicImage, roughnessImage, occlusionImage, options); - var packOcclusion = defined(occlusionImage) && options.packOcclusion; + var metallicRoughnessTexture; + if (defined(options.metallicRoughnessOcclusionTexture)) { + metallicRoughnessTexture = getTexture(gltf, metallicImage); + } else { + metallicRoughnessTexture = createMetallicRoughnessTexture(gltf, metallicImage, roughnessImage, occlusionImage, options); + } + + var packOcclusion = defined(occlusionImage) && options.packOcclusion || defined(options.metallicRoughnessOcclusionTexture); var occlusionTexture = packOcclusion ? metallicRoughnessTexture : getTexture(gltf, occlusionImage); var emissiveFactor = getEmissiveFactor(material); @@ -622,13 +653,13 @@ function convertTraditionalToMetallicRoughness(material) { material.specularShininess = roughnessFactor; } -function createMaterialsCommonMaterial(gltf, images, material, hasNormals) { +function createMaterialsCommonMaterial(gltf, images, material, hasNormals, options) { var materialName = material.name; - var ambientImage = getImage(images, material.ambientTexture); - var diffuseImage = getImage(images, material.diffuseTexture); - var emissiveImage = getImage(images, material.emissiveTexture); - var specularImage = getImage(images, material.specularTexture); + var ambientImage = getImage(images, material.ambientTexture, undefined, options); + var diffuseImage = getImage(images, material.diffuseTexture, undefined, options); + var emissiveImage = getImage(images, material.emissiveTexture, undefined, options); + var specularImage = getImage(images, material.specularTexture, undefined, options); var ambient = defaultValue(getTexture(gltf, ambientImage), material.ambientColor); var diffuse = defaultValue(getTexture(gltf, diffuseImage), material.diffuseColor); @@ -697,7 +728,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); + gltfMaterial = createMaterialsCommonMaterial(gltf, images, material, hasNormals, options); } else { convertTraditionalToMetallicRoughness(material); gltfMaterial = createMetallicRoughnessMaterial(gltf, images, material, options); diff --git a/lib/obj2gltf.js b/lib/obj2gltf.js index 6dbcdca..851e2b4 100644 --- a/lib/obj2gltf.js +++ b/lib/obj2gltf.js @@ -5,13 +5,13 @@ var path = require('path'); var Promise = require('bluebird'); var createGltf = require('./createGltf'); var gltfToGlb = require('./gltfToGlb'); +var loadImage = require('./loadImage'); var loadObj = require('./loadObj'); var writeUris = require('./writeUris'); var defaultValue = Cesium.defaultValue; var defined = Cesium.defined; var DeveloperError = Cesium.DeveloperError; -var RuntimeError = Cesium.RuntimeError; module.exports = obj2gltf; @@ -32,6 +32,13 @@ module.exports = obj2gltf; * @param {Boolean} [options.metallicRoughness=false] The values in the mtl file are already metallic-roughness PBR values and no conversion step should be applied. Metallic is stored in the Ks and map_Ks slots and roughness is stored in the Ns and map_Ns slots. * @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 {String} [options.metallicRoughnessOcclusionTexture] Path to the metallic-roughness-occlusion texture used by the model, where occlusion is stored in the red channel, roughness is stored in the green channel, and metallic is stored in the blue channel. This may be used instead of setting texture paths in the .mtl file. The model will be saved with a pbrMetallicRoughness material. + * @param {String} [options.specularGlossinessTexture] Path to the specular-glossiness texture used by the model, where specular color is stored in the red, green, and blue channels and specular glossiness is stored in the alpha channel. This may be used instead of setting texture paths in the .mtl file. The model will be saved with a material using the KHR_materials_pbrSpecularGlossiness extension. + * @param {String} [options.occlusionTexture] Path to the occlusion texture used by the model. This may be used instead of setting texture paths in the .mtl file. Ignored if metallicRoughnessOcclusionTexture is also set. + * @param {String} [options.normalTexture] Path to the normal texture used by the model. This may be used instead of setting texture paths in the .mtl file. + * @param {String} [options.baseColorTexture] Path to the baseColor/diffuse texture used by the model. This may be used instead of setting texture paths in the .mtl file. + * @param {String} [options.emissiveTexture] Path to the emissive texture used by the model. This may be used instead of setting texture paths in the .mtl file. + * @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. */ @@ -76,6 +83,22 @@ function obj2gltf(objPath, gltfPath, options) { throw new DeveloperError('Only one material type may be set from [--metallicRoughness, --specularGlossiness, --materialsCommon].'); } + if (defined(options.metallicRoughnessOcclusionTexture) && defined(options.specularGlossinessTexture)) { + throw new DeveloperError('options.metallicRoughnessOcclusionTexture and options.specularGlossinessTexture cannot both be defined.'); + } + + if (defined(options.metallicRoughnessOcclusionTexture)) { + options.metallicRoughness = true; + options.specularGlossiness = false; + options.materialsCommon = false; + } + + if (defined(options.specularGlossinessTexture)) { + options.metallicRoughness = false; + options.specularGlossiness = true; + options.materialsCommon = false; + } + var extension = path.extname(gltfPath).toLowerCase(); var modelName = path.basename(gltfPath, path.extname(gltfPath)); if (binary || extension === '.glb') { @@ -89,7 +112,10 @@ function obj2gltf(objPath, gltfPath, options) { spaces : 2 }; - return loadObj(objPath, options) + return loadOverridingImages(options) + .then(function() { + return loadObj(objPath, options); + }) .then(function(objData) { return createGltf(objData, options); }) @@ -105,6 +131,21 @@ function obj2gltf(objPath, gltfPath, options) { }); } +function loadOverridingImages(options) { + // The texture paths supplied in the .mtl may be overriden by the texture path supplied in options + var checkTransparencyOptions = { + checkTransparency : options.checkTransparency + }; + var imagePaths = [options.metallicRoughnessOcclusionTexture, options.specularGlossinessTexture, options.occlusionTexture, options.normalTexture, options.baseColorTexture, options.emissiveTexture]; + imagePaths = imagePaths.filter(function(imagePath) {return defined(imagePath);}); + return Promise.map(imagePaths, function(imagePath) { + var imageOptions = (imagePath === options.baseColorTexture) ? checkTransparencyOptions : undefined; + return loadImage(imagePath, imageOptions); + }).then(function(images) { + options.overridingImages = images; + }); +} + /** * Default values that will be used when calling obj2gltf(options) unless specified in the options object. */ diff --git a/specs/lib/obj2gltfSpec.js b/specs/lib/obj2gltfSpec.js index 1ea5a0e..55183d2 100644 --- a/specs/lib/obj2gltfSpec.js +++ b/specs/lib/obj2gltfSpec.js @@ -85,11 +85,32 @@ describe('obj2gltf', function() { }).toThrowDeveloperError(); }); - it('rejects if more than one material type is set', function(done) { + it('throws if more than one material type is set', function() { var options = { metallicRoughness : true, specularGlossiness : true }; - expect(obj2gltf(objPath, gltfPath, options), done).toRejectWith(RuntimeError); + expect(function() { + obj2gltf(objPath, gltfPath, options); + }).toThrowDeveloperError(); + }); + + it('throws if occlusionTexture is defined and specularGlossinessTexture is undefined', function() { + var options = { + occlusionTexture : 'path/to/occlusion/texture' + }; + expect(function() { + obj2gltf(objPath, gltfPath, options); + }).toThrowDeveloperError(); + }); + + it('throws if metallicRoughnessOcclusionTexture and specularGlossinessTexture are both defined', function() { + var options = { + metallicRoughnessOcclusionTexture : 'path/to/metallic-roughness-occlusion/texture', + specularGlossinessTexture : 'path/to/specular-glossiness/texture' + }; + expect(function() { + obj2gltf(objPath, gltfPath, options); + }).toThrowDeveloperError(); }); });