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`|
|`--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.
|`--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, 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, 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, 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, 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, and is intended for models that use one material.
## Build Instructions

View File

@ -91,32 +91,32 @@ var argv = yargs
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.',
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',
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.',
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',
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.',
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',
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.',
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',
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.',
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',
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.',
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',
normalize: true
}

View File

@ -1,7 +1,6 @@
'use strict';
var Cesium = require('cesium');
var path = require('path');
var PNG = require('pngjs').PNG;
var getBufferPadded = require('./getBufferPadded');
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.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 {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 {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, 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, 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, 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, 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, and is intended for models that use one material.
* @param {Boolean} options.logger A callback function for handling logged messages. Defaults to console.log.
* @returns {Object} A glTF asset.
*
@ -177,7 +176,7 @@ function getImageName(image) {
}
function getTextureName(image) {
return getImageName(image);
return getImageName(image) + '_texture';
}
function addTexture(gltf, image) {
@ -189,10 +188,7 @@ function addTexture(gltf, image) {
gltf.images.push({
name : imageName,
extras : {
_obj2gltf : {
source : image.source,
extension : image.extension
}
_obj2gltf : image
}
});
@ -224,6 +220,7 @@ function getTexture(gltf, image) {
if (!defined(textureIndex)) {
textureIndex = addTexture(gltf, image);
}
return {
index : textureIndex
};
@ -247,9 +244,8 @@ function getEmissiveFactor(material) {
return addColors(ambientColor, emissiveColor);
}
function resizeChannel(sourcePixels, sourceWidth, sourceHeight, targetWidth, targetHeight) {
function resizeChannel(sourcePixels, sourceWidth, sourceHeight, targetPixels, targetWidth, targetHeight) {
// Nearest neighbor sampling
var targetPixels = Buffer.alloc(targetWidth * targetHeight);
var widthRatio = sourceWidth / targetWidth;
var heightRatio = sourceHeight / targetHeight;
@ -266,24 +262,38 @@ function resizeChannel(sourcePixels, sourceWidth, sourceHeight, targetWidth, tar
return targetPixels;
}
function getImageChannel(image, index, targetWidth, targetHeight) {
var scratchResizeChannel;
function getImageChannel(image, index, targetWidth, targetHeight, targetChannel) {
var pixels = image.decoded; // RGBA
var width = image.width;
var height = image.height;
var pixelsLength = width * height;
var channel = Buffer.alloc(pixelsLength);
for (var i = 0; i < pixelsLength; ++i) {
var sourceWidth = image.width;
var sourceHeight = image.height;
var sourcePixelsLength = sourceWidth * sourceHeight;
var targetPixelsLength = targetWidth * targetHeight;
// 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);
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) {
var pixelsLength = width * height;
function writeChannel(pixels, channel, index) {
var pixelsLength = pixels.length / 4;
for (var i = 0; i < pixelsLength; ++i) {
var value = channel.readUInt8(i);
pixels.writeUInt8(value, i * 4 + index);
@ -313,32 +323,6 @@ function getMinimumDimensions(images, options) {
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) {
var packMetallic = defined(metallicImage);
var packRoughness = defined(roughnessImage);
@ -372,40 +356,41 @@ function createMetallicRoughnessTexture(gltf, metallicImage, roughnessImage, occ
var height = dimensions[1];
var pixelsLength = width * height;
var pixels = Buffer.alloc(pixelsLength * 4, 0xFF); // Initialize with 4 channels, unused channels will be white
var scratchChannel = Buffer.alloc(pixelsLength);
if (packMetallic) {
// Write into the B channel
var metallicChannel = getImageChannel(metallicImage, 0, width, height);
writeChannel(pixels, metallicChannel, 2, width, height);
var metallicChannel = getImageChannel(metallicImage, 0, width, height, scratchChannel);
writeChannel(pixels, metallicChannel, 2);
}
if (packRoughness) {
// Write into the G channel
var roughnessChannel = getImageChannel(roughnessImage, 0, width, height);
writeChannel(pixels, roughnessChannel, 1, width, height);
var roughnessChannel = getImageChannel(roughnessImage, 0, width, height, scratchChannel);
writeChannel(pixels, roughnessChannel, 1);
}
if (packOcclusion) {
// Write into the R channel
var occlusionChannel = getImageChannel(occlusionImage, 0, width, height);
writeChannel(pixels, occlusionChannel, 0, width, height);
var occlusionChannel = getImageChannel(occlusionImage, 0, width, height, scratchChannel);
writeChannel(pixels, occlusionChannel, 0);
}
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));
imageNames[i] = getImageName(packedImages[i]);
}
var imageName = imageNames.join('_');
var pngSource = encodePng(pixels, width, height, 4, 3);
var image = {
transparent : false,
source : pngSource,
source : undefined,
extension : '.png',
path : imageName,
extension : '.png'
decoded : pixels,
width : width,
height : height
};
return getTexture(gltf, image);
@ -438,38 +423,39 @@ function createSpecularGlossinessTexture(gltf, specularImage, glossinessImage, o
var height = dimensions[1];
var pixelsLength = width * height;
var pixels = Buffer.alloc(pixelsLength * 4, 0xFF); // Initialize with 4 channels, unused channels will be white
var scratchChannel = Buffer.alloc(pixelsLength);
if (packSpecular) {
// Write into the R, G, B channels
var redChannel = getImageChannel(specularImage, 0, width, height);
var greenChannel = getImageChannel(specularImage, 1, width, height);
var blueChannel = getImageChannel(specularImage, 2, width, height);
writeChannel(pixels, redChannel, 0, width, height);
writeChannel(pixels, greenChannel, 1, width, height);
writeChannel(pixels, blueChannel, 2, width, height);
var redChannel = getImageChannel(specularImage, 0, width, height, scratchChannel);
var greenChannel = getImageChannel(specularImage, 1, width, height, scratchChannel);
var blueChannel = getImageChannel(specularImage, 2, width, height, scratchChannel);
writeChannel(pixels, redChannel, 0);
writeChannel(pixels, greenChannel, 1);
writeChannel(pixels, blueChannel, 2);
}
if (packGlossiness) {
// Write into the A channel
var glossinessChannel = getImageChannel(glossinessImage, 0, width, height);
writeChannel(pixels, glossinessChannel, 3, width, height);
var glossinessChannel = getImageChannel(glossinessImage, 0, width, height, scratchChannel);
writeChannel(pixels, glossinessChannel, 3);
}
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));
imageNames[i] = getImageName(packedImages[i]);
}
var imageName = imageNames.join('_');
var pngSource = encodePng(pixels, width, height, 4, 4);
var image = {
transparent : false,
source : pngSource,
transparent : true,
source : undefined,
extension : '.png',
path : imageName,
extension : '.png'
decoded : pixels,
width : width,
height : height
};
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) {
// Translate the blinn-phong model to the pbr metallic-roughness model
// Roughness factor is a combination of specular intensity and shininess
// Metallic factor is 0.0
// This does not convert textures
var specularIntensity = material.specularColor[0];
var specularIntensity = luminance(material.specularColor);
var specularShininess = material.specularShininess;
// 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.
*
* 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.
* @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 path = require('path');
var PNG = require('pngjs').PNG;
var Promise = require('bluebird');
var defaultValue = Cesium.defaultValue;
var defined = Cesium.defined;
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) {
// Color type is encoded in the 25th bit of the png
var colorType = data[25];
@ -84,13 +98,16 @@ function getPngInfo(data, info, options) {
var decode = options.decode || checkTransparency;
if (decode) {
var decodedResults = PNG.sync.read(data);
info.decoded = decodedResults.data;
info.width = decodedResults.width;
info.height = decodedResults.height;
if (checkTransparency) {
info.transparent = hasTransparency(info);
}
return parsePng(data)
.then(function(decodedResults) {
info.decoded = decodedResults.data;
info.width = decodedResults.width;
info.height = decodedResults.height;
if (checkTransparency) {
info.transparent = hasTransparency(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.');
});
}, {concurrency : 10})
.then(function() {
return images;
});
.thenReturn(images);
}
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.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 {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, 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, 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, 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, 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, and is intended for models that use one material.
* @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.
@ -108,10 +108,6 @@ function obj2gltf(objPath, gltfPath, options) {
gltfPath = path.join(path.dirname(gltfPath), modelName + extension);
var jsonOptions = {
spaces : 2
};
return loadOverridingImages(options)
.then(function() {
return loadObj(objPath, options);
@ -127,6 +123,9 @@ function obj2gltf(objPath, gltfPath, options) {
var glb = gltfToGlb(gltf);
return fsExtra.outputFile(gltfPath, glb);
}
var jsonOptions = {
spaces : 2
};
return fsExtra.outputJson(gltfPath, gltf, jsonOptions);
});
}

View File

@ -3,6 +3,7 @@ var Cesium = require('cesium');
var fsExtra = require('fs-extra');
var mime = require('mime');
var path = require('path');
var PNG = require('pngjs').PNG;
var Promise = require('bluebird');
var getBufferPadded = require('./getBufferPadded');
@ -24,50 +25,102 @@ module.exports = writeUris;
* @private
*/
function writeUris(gltf, gltfPath, options) {
var separate = options.separate;
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)
return encodeImages(gltf)
.then(function() {
deleteExtras(gltf);
cleanup(gltf);
return gltf;
var separate = options.separate;
var separateTextures = options.separateTextures;
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) {
var buffer = gltf.buffers[0];
delete buffer.extras;

View File

@ -92,6 +92,8 @@ describe('createGltf', function() {
var gltf = createGltf(boxObjData, defaultOptions);
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.meshes.length).toBe(1);
@ -114,6 +116,8 @@ describe('createGltf', function() {
var gltf = createGltf(groupObjData, defaultOptions);
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[0].mesh).toBeUndefined();
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() {
var options = clone(defaultOptions);
options.specularGlossiness = true;
@ -440,7 +444,7 @@ describe('createGltf', function() {
expect(kmc.values.doubleSided).toBe(false);
expect(texture).toEqual({
name : 'cesium',
name : 'cesium_texture',
sampler : 0,
source : 0
});