Async image reading/writing and other cleanup

This commit is contained in:
Sean Lilley 2017-07-27 11:23:12 -04:00
parent 125edddef9
commit e54f3af37f
9 changed files with 217 additions and 154 deletions

View File

@ -45,12 +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`| |`--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`| |`--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`| |`--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. |`--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, and is intended for models that use one material. 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. |`--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, and is intended for models that use one material. 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. |`--occlusionTexture`|Path to the occlusion texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material. 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. |`--normalTexture`|Path to the normal texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.
|`--baseColorTexture`|Path to the baseColor/diffuse 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, and is intended for models that use one material.
|`--emissiveTexture`|Path to the emissive 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, and is intended for models that use one material.
## Build Instructions ## Build Instructions

View File

@ -91,32 +91,32 @@ var argv = yargs
default: defaults.materialsCommon default: defaults.materialsCommon
}, },
metallicRoughnessOcclusionTexture : { 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.', 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, and is intended for models that use one material. The model will be saved with a pbrMetallicRoughness material.',
type: 'string', type: 'string',
normalize: true normalize: true
}, },
specularGlossinessTexture : { 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.', 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, and is intended for models that use one material. The model will be saved with a material using the KHR_materials_pbrSpecularGlossiness extension.',
type: 'string', type: 'string',
normalize: true normalize: true
}, },
occlusionTexture : { 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.', describe: 'Path to the occlusion texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material. Ignored if metallicRoughnessOcclusionTexture is also set.',
type: 'string', type: 'string',
normalize: true normalize: true
}, },
normalTexture : { normalTexture : {
describe: 'Path to the normal texture used by the model. This may be used instead of setting texture paths in the .mtl file.', describe: 'Path to the normal texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.',
type: 'string', type: 'string',
normalize: true normalize: true
}, },
baseColorTexture : { 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.', describe: 'Path to the baseColor/diffuse texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.',
type: 'string', type: 'string',
normalize: true normalize: true
}, },
emissiveTexture : { emissiveTexture : {
describe: 'Path to the emissive texture used by the model. This may be used instead of setting texture paths in the .mtl file.', describe: 'Path to the emissive texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.',
type: 'string', type: 'string',
normalize: true normalize: true
} }

View File

@ -1,7 +1,6 @@
'use strict'; 'use strict';
var Cesium = require('cesium'); var Cesium = require('cesium');
var path = require('path'); var path = require('path');
var PNG = require('pngjs').PNG;
var getBufferPadded = require('./getBufferPadded'); var getBufferPadded = require('./getBufferPadded');
var Material = require('./Material'); var Material = require('./Material');
@ -22,12 +21,12 @@ module.exports = createGltf;
* @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.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 {Boolean} options.materialsCommon The glTF will be saved with the KHR_materials_common extension.
* @param {Object[]} options.overridingImages An array of images that override images in the .mtl file. * @param {Object[]} options.overridingImages An array of images that override images in the .mtl file.
* @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.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, and is intended for models that use one material. 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.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, and is intended for models that use one material. 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.occlusionTexture] Path to the occlusion texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material. 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.normalTexture] Path to the normal texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.
* @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.baseColorTexture] Path to the baseColor/diffuse texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.
* @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 {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, and is intended for models that use one material.
* @param {Boolean} options.logger A callback function for handling logged messages. Defaults to console.log. * @param {Boolean} options.logger A callback function for handling logged messages. Defaults to console.log.
* @returns {Object} A glTF asset. * @returns {Object} A glTF asset.
* *
@ -177,7 +176,7 @@ function getImageName(image) {
} }
function getTextureName(image) { function getTextureName(image) {
return getImageName(image); return getImageName(image) + '_texture';
} }
function addTexture(gltf, image) { function addTexture(gltf, image) {
@ -189,10 +188,7 @@ function addTexture(gltf, image) {
gltf.images.push({ gltf.images.push({
name : imageName, name : imageName,
extras : { extras : {
_obj2gltf : { _obj2gltf : image
source : image.source,
extension : image.extension
}
} }
}); });
@ -224,6 +220,7 @@ function getTexture(gltf, image) {
if (!defined(textureIndex)) { if (!defined(textureIndex)) {
textureIndex = addTexture(gltf, image); textureIndex = addTexture(gltf, image);
} }
return { return {
index : textureIndex index : textureIndex
}; };
@ -247,9 +244,8 @@ function getEmissiveFactor(material) {
return addColors(ambientColor, emissiveColor); return addColors(ambientColor, emissiveColor);
} }
function resizeChannel(sourcePixels, sourceWidth, sourceHeight, targetWidth, targetHeight) { function resizeChannel(sourcePixels, sourceWidth, sourceHeight, targetPixels, targetWidth, targetHeight) {
// Nearest neighbor sampling // Nearest neighbor sampling
var targetPixels = Buffer.alloc(targetWidth * targetHeight);
var widthRatio = sourceWidth / targetWidth; var widthRatio = sourceWidth / targetWidth;
var heightRatio = sourceHeight / targetHeight; var heightRatio = sourceHeight / targetHeight;
@ -266,24 +262,38 @@ function resizeChannel(sourcePixels, sourceWidth, sourceHeight, targetWidth, tar
return targetPixels; return targetPixels;
} }
function getImageChannel(image, index, targetWidth, targetHeight) { var scratchResizeChannel;
function getImageChannel(image, index, targetWidth, targetHeight, targetChannel) {
var pixels = image.decoded; // RGBA var pixels = image.decoded; // RGBA
var width = image.width; var sourceWidth = image.width;
var height = image.height; var sourceHeight = image.height;
var pixelsLength = width * height; var sourcePixelsLength = sourceWidth * sourceHeight;
var channel = Buffer.alloc(pixelsLength); var targetPixelsLength = targetWidth * targetHeight;
for (var i = 0; i < pixelsLength; ++i) {
// Allocate the scratchResizeChannel on demand if the texture needs to be resized
var sourceChannel = targetChannel;
if (sourcePixelsLength > targetPixelsLength) {
if (!defined(scratchResizeChannel) || (sourcePixelsLength > scratchResizeChannel.length)) {
scratchResizeChannel = Buffer.alloc(sourcePixelsLength);
}
sourceChannel = scratchResizeChannel;
}
for (var i = 0; i < sourcePixelsLength; ++i) {
var value = pixels.readUInt8(i * 4 + index); var value = pixels.readUInt8(i * 4 + index);
channel.writeUInt8(value, i); sourceChannel.writeUInt8(value, i);
} }
if (width !== targetWidth || height !== targetHeight) {
channel = resizeChannel(channel, width, height, targetWidth, targetHeight); if (sourcePixelsLength > targetPixelsLength) {
resizeChannel(sourceChannel, sourceWidth, sourceHeight, targetChannel, targetWidth, targetHeight);
} }
return channel;
return targetChannel;
} }
function writeChannel(pixels, channel, index, width, height) { function writeChannel(pixels, channel, index) {
var pixelsLength = width * height; var pixelsLength = pixels.length / 4;
for (var i = 0; i < pixelsLength; ++i) { for (var i = 0; i < pixelsLength; ++i) {
var value = channel.readUInt8(i); var value = channel.readUInt8(i);
pixels.writeUInt8(value, i * 4 + index); pixels.writeUInt8(value, i * 4 + index);
@ -313,32 +323,6 @@ function getMinimumDimensions(images, options) {
return [width, height]; return [width, height];
} }
function encodePng(pixels, width, height, inputChannels, outputChannels) {
var pngInput = {
data : pixels,
width : width,
height : height
};
// Constants defined by pngjs
var rgbColorType = 2;
var rgbaColorType = 6;
var colorType = outputChannels === 4 ? rgbaColorType : rgbColorType;
var inputColorType = inputChannels === 4 ? rgbaColorType : rgbColorType;
var inputHasAlpha = inputChannels === 4;
var pngOptions = {
width : width,
height : height,
colorType : colorType,
inputColorType : inputColorType,
inputHasAlpha : inputHasAlpha
};
return PNG.sync.write(pngInput, pngOptions);
}
function createMetallicRoughnessTexture(gltf, metallicImage, roughnessImage, occlusionImage, options) { function createMetallicRoughnessTexture(gltf, metallicImage, roughnessImage, occlusionImage, options) {
var packMetallic = defined(metallicImage); var packMetallic = defined(metallicImage);
var packRoughness = defined(roughnessImage); var packRoughness = defined(roughnessImage);
@ -372,40 +356,41 @@ function createMetallicRoughnessTexture(gltf, metallicImage, roughnessImage, occ
var height = dimensions[1]; var height = dimensions[1];
var pixelsLength = width * height; var pixelsLength = width * height;
var pixels = Buffer.alloc(pixelsLength * 4, 0xFF); // Initialize with 4 channels, unused channels will be white var pixels = Buffer.alloc(pixelsLength * 4, 0xFF); // Initialize with 4 channels, unused channels will be white
var scratchChannel = Buffer.alloc(pixelsLength);
if (packMetallic) { if (packMetallic) {
// Write into the B channel // Write into the B channel
var metallicChannel = getImageChannel(metallicImage, 0, width, height); var metallicChannel = getImageChannel(metallicImage, 0, width, height, scratchChannel);
writeChannel(pixels, metallicChannel, 2, width, height); writeChannel(pixels, metallicChannel, 2);
} }
if (packRoughness) { if (packRoughness) {
// Write into the G channel // Write into the G channel
var roughnessChannel = getImageChannel(roughnessImage, 0, width, height); var roughnessChannel = getImageChannel(roughnessImage, 0, width, height, scratchChannel);
writeChannel(pixels, roughnessChannel, 1, width, height); writeChannel(pixels, roughnessChannel, 1);
} }
if (packOcclusion) { if (packOcclusion) {
// Write into the R channel // Write into the R channel
var occlusionChannel = getImageChannel(occlusionImage, 0, width, height); var occlusionChannel = getImageChannel(occlusionImage, 0, width, height, scratchChannel);
writeChannel(pixels, occlusionChannel, 0, width, height); writeChannel(pixels, occlusionChannel, 0);
} }
var length = packedImages.length; var length = packedImages.length;
var imageNames = new Array(length); var imageNames = new Array(length);
for (var i = 0; i < length; ++i) { for (var i = 0; i < length; ++i) {
var imagePath = packedImages[i].path; imageNames[i] = getImageName(packedImages[i]);
imageNames[i] = path.basename(imagePath, path.extname(imagePath));
} }
var imageName = imageNames.join('_'); var imageName = imageNames.join('_');
var pngSource = encodePng(pixels, width, height, 4, 3);
var image = { var image = {
transparent : false, transparent : false,
source : pngSource, source : undefined,
extension : '.png',
path : imageName, path : imageName,
extension : '.png' decoded : pixels,
width : width,
height : height
}; };
return getTexture(gltf, image); return getTexture(gltf, image);
@ -438,38 +423,39 @@ function createSpecularGlossinessTexture(gltf, specularImage, glossinessImage, o
var height = dimensions[1]; var height = dimensions[1];
var pixelsLength = width * height; var pixelsLength = width * height;
var pixels = Buffer.alloc(pixelsLength * 4, 0xFF); // Initialize with 4 channels, unused channels will be white var pixels = Buffer.alloc(pixelsLength * 4, 0xFF); // Initialize with 4 channels, unused channels will be white
var scratchChannel = Buffer.alloc(pixelsLength);
if (packSpecular) { if (packSpecular) {
// Write into the R, G, B channels // Write into the R, G, B channels
var redChannel = getImageChannel(specularImage, 0, width, height); var redChannel = getImageChannel(specularImage, 0, width, height, scratchChannel);
var greenChannel = getImageChannel(specularImage, 1, width, height); var greenChannel = getImageChannel(specularImage, 1, width, height, scratchChannel);
var blueChannel = getImageChannel(specularImage, 2, width, height); var blueChannel = getImageChannel(specularImage, 2, width, height, scratchChannel);
writeChannel(pixels, redChannel, 0, width, height); writeChannel(pixels, redChannel, 0);
writeChannel(pixels, greenChannel, 1, width, height); writeChannel(pixels, greenChannel, 1);
writeChannel(pixels, blueChannel, 2, width, height); writeChannel(pixels, blueChannel, 2);
} }
if (packGlossiness) { if (packGlossiness) {
// Write into the A channel // Write into the A channel
var glossinessChannel = getImageChannel(glossinessImage, 0, width, height); var glossinessChannel = getImageChannel(glossinessImage, 0, width, height, scratchChannel);
writeChannel(pixels, glossinessChannel, 3, width, height); writeChannel(pixels, glossinessChannel, 3);
} }
var length = packedImages.length; var length = packedImages.length;
var imageNames = new Array(length); var imageNames = new Array(length);
for (var i = 0; i < length; ++i) { for (var i = 0; i < length; ++i) {
var imagePath = packedImages[i].path; imageNames[i] = getImageName(packedImages[i]);
imageNames[i] = path.basename(imagePath, path.extname(imagePath));
} }
var imageName = imageNames.join('_'); var imageName = imageNames.join('_');
var pngSource = encodePng(pixels, width, height, 4, 4);
var image = { var image = {
transparent : false, transparent : true,
source : pngSource, source : undefined,
extension : '.png',
path : imageName, path : imageName,
extension : '.png' decoded : pixels,
width : width,
height : height
}; };
return getTexture(gltf, image); return getTexture(gltf, image);
@ -628,12 +614,16 @@ function createMetallicRoughnessMaterial(gltf, images, material, options) {
}; };
} }
function luminance(color) {
return color[0] * 0.2125 + color[1] * 0.7154 + color[2] * 0.0721;
}
function convertTraditionalToMetallicRoughness(material) { function convertTraditionalToMetallicRoughness(material) {
// Translate the blinn-phong model to the pbr metallic-roughness model // Translate the blinn-phong model to the pbr metallic-roughness model
// Roughness factor is a combination of specular intensity and shininess // Roughness factor is a combination of specular intensity and shininess
// Metallic factor is 0.0 // Metallic factor is 0.0
// This does not convert textures // This does not convert textures
var specularIntensity = material.specularColor[0]; var specularIntensity = luminance(material.specularColor);
var specularShininess = material.specularShininess; var specularShininess = material.specularShininess;
// Transform from 0-1000 range to 0-1 range. Then invert. // Transform from 0-1000 range to 0-1 range. Then invert.

View File

@ -9,6 +9,8 @@ module.exports = gltfToGlb;
/** /**
* Convert a glTF to binary glTF. * Convert a glTF to binary glTF.
* *
* The glTF is expected to have all resources embedded as bufferViews and a single buffer whose content is stored in a data uri.
*
* @param {Object} gltf A javascript object containing a glTF asset. * @param {Object} gltf A javascript object containing a glTF asset.
* @returns {Promise} A promise that resolves to a buffer containing the binary glTF. * @returns {Promise} A promise that resolves to a buffer containing the binary glTF.
* *

View File

@ -4,8 +4,10 @@ var fsExtra = require('fs-extra');
var jpeg = require('jpeg-js'); var jpeg = require('jpeg-js');
var path = require('path'); var path = require('path');
var PNG = require('pngjs').PNG; var PNG = require('pngjs').PNG;
var Promise = require('bluebird');
var defaultValue = Cesium.defaultValue; var defaultValue = Cesium.defaultValue;
var defined = Cesium.defined;
module.exports = loadImage; module.exports = loadImage;
@ -75,6 +77,18 @@ function getChannels(colorType) {
} }
} }
function parsePng(data) {
return new Promise(function(resolve, reject) {
new PNG().parse(data, function(error, decodedResults) {
if (defined(error)) {
reject(error);
return;
}
resolve(decodedResults);
});
});
}
function getPngInfo(data, info, options) { function getPngInfo(data, info, options) {
// Color type is encoded in the 25th bit of the png // Color type is encoded in the 25th bit of the png
var colorType = data[25]; var colorType = data[25];
@ -84,13 +98,16 @@ function getPngInfo(data, info, options) {
var decode = options.decode || checkTransparency; var decode = options.decode || checkTransparency;
if (decode) { if (decode) {
var decodedResults = PNG.sync.read(data); return parsePng(data)
info.decoded = decodedResults.data; .then(function(decodedResults) {
info.width = decodedResults.width; info.decoded = decodedResults.data;
info.height = decodedResults.height; info.width = decodedResults.width;
if (checkTransparency) { info.height = decodedResults.height;
info.transparent = hasTransparency(info); if (checkTransparency) {
} info.transparent = hasTransparency(info);
}
return info;
});
} }
return info; return info;
} }

View File

@ -368,9 +368,7 @@ function loadImages(imagesOptions, objPath, options) {
logger('Could not read image file at ' + imagePath + '. Material will ignore this image.'); logger('Could not read image file at ' + imagePath + '. Material will ignore this image.');
}); });
}, {concurrency : 10}) }, {concurrency : 10})
.then(function() { .thenReturn(images);
return images;
});
} }
function getImagesOptions(materials, options) { function getImagesOptions(materials, options) {

View File

@ -32,12 +32,12 @@ 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.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.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 {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.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, and is intended for models that use one material. 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.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, and is intended for models that use one material. 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.occlusionTexture] Path to the occlusion texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material. 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.normalTexture] Path to the normal texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.
* @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.baseColorTexture] Path to the baseColor/diffuse texture used by the model. This may be used instead of setting texture paths in the .mtl file, and is intended for models that use one material.
* @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 {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, and is intended for models that use one material.
* @param {Logger} [options.logger] A callback function for handling logged messages. Defaults to console.log. * @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. * @return {Promise} A promise that resolves when the glTF file is saved.
@ -108,10 +108,6 @@ function obj2gltf(objPath, gltfPath, options) {
gltfPath = path.join(path.dirname(gltfPath), modelName + extension); gltfPath = path.join(path.dirname(gltfPath), modelName + extension);
var jsonOptions = {
spaces : 2
};
return loadOverridingImages(options) return loadOverridingImages(options)
.then(function() { .then(function() {
return loadObj(objPath, options); return loadObj(objPath, options);
@ -127,6 +123,9 @@ function obj2gltf(objPath, gltfPath, options) {
var glb = gltfToGlb(gltf); var glb = gltfToGlb(gltf);
return fsExtra.outputFile(gltfPath, glb); return fsExtra.outputFile(gltfPath, glb);
} }
var jsonOptions = {
spaces : 2
};
return fsExtra.outputJson(gltfPath, gltf, jsonOptions); return fsExtra.outputJson(gltfPath, gltf, jsonOptions);
}); });
} }

View File

@ -3,6 +3,7 @@ var Cesium = require('cesium');
var fsExtra = require('fs-extra'); var fsExtra = require('fs-extra');
var mime = require('mime'); var mime = require('mime');
var path = require('path'); var path = require('path');
var PNG = require('pngjs').PNG;
var Promise = require('bluebird'); var Promise = require('bluebird');
var getBufferPadded = require('./getBufferPadded'); var getBufferPadded = require('./getBufferPadded');
@ -24,50 +25,102 @@ module.exports = writeUris;
* @private * @private
*/ */
function writeUris(gltf, gltfPath, options) { function writeUris(gltf, gltfPath, options) {
var separate = options.separate; return encodeImages(gltf)
var separateTextures = options.separateTextures;
var promises = [];
var buffer = gltf.buffers[0];
var bufferByteLength = buffer.extras._obj2gltf.source.length;
var texturesByteLength = 0;
var images = gltf.images;
var imagesLength = images.length;
for (var i = 0; i < imagesLength; ++i) {
texturesByteLength += images[i].extras._obj2gltf.source.length;
}
// Buffers larger than ~192MB cannot be base64 encoded due to a NodeJS limitation. Source: https://github.com/nodejs/node/issues/4266
var exceedsMaximum = (texturesByteLength + bufferByteLength > 201326580);
if (exceedsMaximum && !separate) {
return Promise.reject(new RuntimeError('Buffers and textures are too large to encode in the glTF. Use the --separate flag instead.'));
}
var name = path.basename(gltfPath, path.extname(gltfPath));
if (separateTextures) {
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() { .then(function() {
deleteExtras(gltf); var separate = options.separate;
cleanup(gltf); var separateTextures = options.separateTextures;
return gltf;
var buffer = gltf.buffers[0];
var bufferByteLength = buffer.extras._obj2gltf.source.length;
var texturesByteLength = 0;
var images = gltf.images;
var imagesLength = images.length;
for (var i = 0; i < imagesLength; ++i) {
texturesByteLength += images[i].extras._obj2gltf.source.length;
}
// Buffers larger than ~192MB cannot be base64 encoded due to a NodeJS limitation. Source: https://github.com/nodejs/node/issues/4266
var exceedsMaximum = (texturesByteLength + bufferByteLength > 201326580);
if (exceedsMaximum && !separate) {
return Promise.reject(new RuntimeError('Buffers and textures are too large to encode in the glTF. Use the --separate flag instead.'));
}
var name = path.basename(gltfPath, path.extname(gltfPath));
var promises = [];
if (separateTextures) {
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);
cleanup(gltf);
return gltf;
});
}); });
} }
function encodePng(image) {
// Constants defined by pngjs
var rgbColorType = 2;
var rgbaColorType = 6;
var png = new PNG({
width : image.width,
height : image.height,
colorType : image.transparent ? rgbaColorType : rgbColorType,
inputColorType : rgbaColorType,
inputHasAlpha : true
});
png.data = image.decoded;
return new Promise(function(resolve, reject) {
var chunks = [];
var stream = png.pack();
stream.on('data', function(chunk) {
chunks.push(chunk);
});
stream.on('end', function() {
resolve(Buffer.concat(chunks));
});
stream.on('error', reject);
});
}
function encodeImage(image) {
var imageExtras = image.extras._obj2gltf;
if (!defined(imageExtras.source) && defined(imageExtras.decoded) && imageExtras.extension === '.png') {
return encodePng(imageExtras)
.then(function(encoded) {
imageExtras.source = encoded;
});
}
}
function encodeImages(gltf) {
// Dynamically generated metallicRoughnessOcclusion and specularGlossiness
// textures need to be encoded to png's prior to being saved.
var encodePromises = [];
var images = gltf.images;
var length = images.length;
for (var i = 0; i < length; ++i) {
encodePromises.push(encodeImage(images[i]));
}
return Promise.all(encodePromises);
}
function deleteExtras(gltf) { function deleteExtras(gltf) {
var buffer = gltf.buffers[0]; var buffer = gltf.buffers[0];
delete buffer.extras; delete buffer.extras;

View File

@ -92,6 +92,8 @@ describe('createGltf', function() {
var gltf = createGltf(boxObjData, defaultOptions); var gltf = createGltf(boxObjData, defaultOptions);
expect(gltf.materials.length).toBe(1); expect(gltf.materials.length).toBe(1);
expect(gltf.scene).toBe(0);
expect(gltf.scenes[0].nodes[0]).toBe(0);
expect(gltf.nodes.length).toBe(1); expect(gltf.nodes.length).toBe(1);
expect(gltf.meshes.length).toBe(1); expect(gltf.meshes.length).toBe(1);
@ -114,6 +116,8 @@ describe('createGltf', function() {
var gltf = createGltf(groupObjData, defaultOptions); var gltf = createGltf(groupObjData, defaultOptions);
expect(gltf.materials.length).toBe(3); expect(gltf.materials.length).toBe(3);
expect(gltf.scene).toBe(0);
expect(gltf.scenes[0].nodes[0]).toBe(0);
expect(gltf.nodes.length).toBe(4); expect(gltf.nodes.length).toBe(4);
expect(gltf.nodes[0].mesh).toBeUndefined(); expect(gltf.nodes[0].mesh).toBeUndefined();
expect(gltf.nodes[0].children.length).toBe(3); expect(gltf.nodes[0].children.length).toBe(3);
@ -320,7 +324,7 @@ describe('createGltf', function() {
}); });
}); });
describe('specularGlosiness', function() { describe('specularGlossiness', function() {
it('sets default material values for specularGlossiness', function() { it('sets default material values for specularGlossiness', function() {
var options = clone(defaultOptions); var options = clone(defaultOptions);
options.specularGlossiness = true; options.specularGlossiness = true;
@ -440,7 +444,7 @@ describe('createGltf', function() {
expect(kmc.values.doubleSided).toBe(false); expect(kmc.values.doubleSided).toBe(false);
expect(texture).toEqual({ expect(texture).toEqual({
name : 'cesium', name : 'cesium_texture',
sampler : 0, sampler : 0,
source : 0 source : 0
}); });