Many updates

This commit is contained in:
Sean Lilley 2017-04-10 17:57:56 -04:00
parent 28d081e0ae
commit cc8fee19c4
16 changed files with 515 additions and 564 deletions

View File

@ -11,45 +11,40 @@ npm install --save obj2gltf
Using obj2gltf as a library: Using obj2gltf as a library:
```javascript ```javascript
var obj2gltf = require('obj2gltf'); var obj2gltf = require('obj2gltf');
var convert = obj2gltf.convert;
var options = { var options = {
separateTextures : true // Don't embed textures in the converted glTF separateTextures : true // Don't embed textures in the converted glTF
} }
convert('model.obj', 'model.gltf', options) obj2gltf('model.obj', 'model.gltf', options)
.then(function() { .then(function() {
console.log('Converted model'); console.log('Converted model');
}); });
``` ```
Using obj2gltf as a command-line tool: Using obj2gltf as a command-line tool:
`node bin/obj2gltf.js model.obj` `node bin/obj2gltf.js -i model.obj`
`node bin/obj2gltf.js model.obj model.gltf`
`node bin/obj2gltf.js -i model.obj -o model.gltf` `node bin/obj2gltf.js -i model.obj -o model.gltf`
`node bin/obj2gltf.js -i model.obj -o model.gltf -s`
## Usage ## Usage
###Command line flags: ###Command line flags:
|Flag|Description|Required| |Flag|Description|Required|
|----|-----------|--------| |----|-----------|--------|
|`-h`|Display help.|No| |`-h`, `--help`|Display help.|No|
|`-i`|Path to the obj file.| :white_check_mark: Yes| |`-i`, `--input`|Path to the obj file.| :white_check_mark: Yes|
|`-o`|Path of the converted glTF file.|No| |`-o`, `--output`|Path of the converted glTF file.|No|
|`-b`|Save as binary glTF.|No, default `false`| |`-b`, `--binary`|Save as binary glTF.|No, default `false`|
|`-s`|Writes out separate geometry data files, shader files, and textures instead of embedding them in the glTF file.|No, default `false`| |`-s`, `--separate`|Writes out separate geometry data files, shader files, and textures instead of embedding them in the glTF file.|No, default `false`|
|`-t`|Write out separate textures only.|No, default `false`| |`-t`, `--separateTextures`|Write out separate textures only.|No, default `false`|
|`-c`|Quantize positions, compress texture coordinates, and oct-encode normals.|No, default `false`| |`-c`, `--compress`|Quantize positions, compress texture coordinates, and oct-encode normals.|No, default `false`|
|`-z`|Use the optimization stages in the glTF pipeline.|No, default `false`| |`-z`, `--optimize`|Use the optimization stages in the glTF pipeline.|No, default `false`|
|`-n`|Generate normals if they are missing.|No, default `false`| |`-n`, `--generateNormals`|Generate normals if they are missing.|No, default `false`|
|`--cesium`|Optimize the glTF for Cesium by using the sun as a default light source.|No, default `false`| |`--optimizeForCesium`|Optimize the glTF for Cesium by using the sun as a default light source.|No, default `false`|
|`--ao`|Apply ambient occlusion to the converted model.|No, default `false`| |`--ao`|Apply ambient occlusion to the converted model.|No, default `false`|
|`--kmc|Output glTF with the KHR_materials_common extension.|No, default `false`| |`--kmc|Output glTF with the KHR_materials_common extension.|No, default `false`|
|`--bypassPipeline`|Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension.|No, default `false`| |`--bypassPipeline`|Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension.|No, default `false`|
|`--hasTransparency`|Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures with an alpha channel are considered to be transparent.|No, default `false`| |`--checkTransparency`|Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures are considered to be opaque.|No, default `false`|
|`--secure`|Prevent the converter from reading image or mtl files outside of the input obj directory.|No, default `false`| |`--secure`|Prevent the converter from reading image or mtl files outside of the input obj directory.|No, default `false`|
## Build Instructions ## Build Instructions

View File

@ -5,11 +5,11 @@ var path = require('path');
var yargs = require('yargs'); var yargs = require('yargs');
var convert = require('../lib/convert'); var convert = require('../lib/convert');
var defaultValue = Cesium.defaultValue;
var defined = Cesium.defined; var defined = Cesium.defined;
var defaults = convert.defaults;
var args = process.argv; var args = process.argv;
args = args.slice(2, args.length);
var argv = yargs var argv = yargs
.usage('Usage: node $0 -i inputPath -o outputPath') .usage('Usage: node $0 -i inputPath -o outputPath')
@ -21,7 +21,8 @@ var argv = yargs
alias: 'i', alias: 'i',
describe: 'Path to the obj file.', describe: 'Path to the obj file.',
type: 'string', type: 'string',
normalize: true normalize: true,
demandOption: true
}, },
output : { output : {
alias: 'o', alias: 'o',
@ -33,77 +34,72 @@ var argv = yargs
alias: 'b', alias: 'b',
describe: 'Save as binary glTF.', describe: 'Save as binary glTF.',
type: 'boolean', type: 'boolean',
default: false default: defaults.binary
}, },
separate : { separate : {
alias: 's', alias: 's',
describe: 'Write separate geometry data files, shader files, and textures instead of embedding them in the glTF.', describe: 'Write separate geometry data files, shader files, and textures instead of embedding them in the glTF.',
type: 'boolean', type: 'boolean',
default: false default: defaults.separate
}, },
separateTextures : { separateTextures : {
alias: 't', alias: 't',
describe: 'Write out separate textures only.', describe: 'Write out separate textures only.',
type: 'boolean', type: 'boolean',
default: false default: defaults.separateTextures
}, },
compress : { compress : {
alias: 'c', alias: 'c',
describe: 'Quantize positions, compress texture coordinates, and oct-encode normals.', describe: 'Quantize positions, compress texture coordinates, and oct-encode normals.',
type: 'boolean', type: 'boolean',
default: false default: defaults.compress
}, },
optimize : { optimize : {
alias: 'z', alias: 'z',
describe: 'Use the optimization stages in the glTF pipeline.', describe: 'Optimize the glTF for size and runtime performance.',
type: 'boolean', type: 'boolean',
default: false default: defaults.optimize
}, },
cesium : { optimizeForCesium : {
describe: 'Optimize the glTF for Cesium by using the sun as a default light source.', describe: 'Optimize the glTF for Cesium by using the sun as a default light source.',
type: 'boolean', type: 'boolean',
default: false default: defaults.optimizeForCesium
}, },
generateNormals : { generateNormals : {
alias: 'n', alias: 'n',
describe: 'Generate normals if they are missing.', describe: 'Generate normals if they are missing.',
type: 'boolean', type: 'boolean',
default: false default: defaults.generateNormals
}, },
ao : { ao : {
describe: 'Apply ambient occlusion to the converted model.', describe: 'Apply ambient occlusion to the converted model.',
type: 'boolean', type: 'boolean',
default: false default: defaults.ao
}, },
kmc : { kmc : {
describe: 'Output glTF with the KHR_materials_common extension.', describe: 'Output glTF with the KHR_materials_common extension.',
type: 'boolean', type: 'boolean',
default: false default: defaults.kmc
}, },
bypassPipeline : { bypassPipeline : {
describe: 'Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension.', describe: 'Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension.',
type: 'boolean', type: 'boolean',
default: false default: defaults.bypassPipeline
}, },
hasTransparency : { checkTransparency : {
describe: 'Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures with an alpha channel are considered to be transparent.', describe: 'Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures are considered to be opaque.',
type: 'boolean', type: 'boolean',
default: false default: defaults.checkTransparency
}, },
secure : { secure : {
describe: 'Prevent the converter from reading image or mtl files outside of the input obj directory.', describe: 'Prevent the converter from reading image or mtl files outside of the input obj directory.',
type: 'boolean', type: 'boolean',
default: false default: defaults.secure
} }
}).parse(args); }).parse(args);
var objPath = defaultValue(argv.i, argv._[0]); var objPath = argv.i;
var gltfPath = defaultValue(argv.o, argv._[1]); var gltfPath = argv.o;
if (!defined(objPath)) {
yargs.showHelp();
return;
}
if (!defined(gltfPath)) { if (!defined(gltfPath)) {
var extension = argv.b ? '.glb' : '.gltf'; var extension = argv.b ? '.glb' : '.gltf';
@ -112,16 +108,17 @@ if (!defined(gltfPath)) {
} }
var options = { var options = {
binary : argv.b, binary : argv.binary,
separate : argv.s, separate : argv.separate,
separateTextures : argv.t, separateTextures : argv.separateTextures,
compress : argv.c, compress : argv.compress,
optimize : argv.z, optimize : argv.optimize,
generateNormals : argv.n, optimizeForCesium : argv.optimizeForCesium,
generateNormals : argv.generateNormals,
ao : argv.ao, ao : argv.ao,
optimizeForCesium : argv.cesium, kmc : argv.kmc,
bypassPipeline : argv.bypassPipeline, bypassPipeline : argv.bypassPipeline,
hasTransparency : argv.hasTransparency, checkTransparency : argv.checkTransparency,
secure : argv.secure secure : argv.secure
}; };

View File

@ -1,3 +1 @@
module.exports = { module.exports = require('./lib/convert');
convert : require('./lib/convert')
};

19
lib/Material.js Normal file
View File

@ -0,0 +1,19 @@
'use strict';
module.exports = Material;
function Material() {
this.ambientColor = [0.0, 0.0, 0.0, 1.0]; // Ka
this.emissionColor = [0.0, 0.0, 0.0, 1.0]; // Ke
this.diffuseColor = [0.5, 0.5, 0.5, 1.0]; // Kd
this.specularColor = [0.0, 0.0, 0.0, 1.0]; // Ks
this.specularShininess = 0.0; // Ns
this.alpha = 1.0; // d / Tr
this.ambientTexture = undefined; // map_Ka
this.emissionTexture = undefined; // map_Ke
this.diffuseTexture = undefined; // map_Kd
this.specularTexture = undefined; // map_Ks
this.specularShininessMap = undefined; // map_Ns
this.normalMap = undefined; // map_Bump
this.alphaMap = undefined; // map_d
}

View File

@ -1,54 +0,0 @@
'use strict';
var Cesium = require('cesium');
var ArrayStorage = require('./ArrayStorage');
var defaultValue = Cesium.defaultValue;
module.exports = clone;
/**
* Clones an object, returning a new object containing the same properties.
* Modified from Cesium.clone to support typed arrays, buffers, and the ArrayStorage class.
*
* @param {Object} object The object to clone.
* @param {Boolean} [deep=false] If true, all properties will be deep cloned recursively.
* @returns {Object} The cloned object.
*
* @private
*/
function clone(object, deep) {
if (object === null || typeof object !== 'object') {
return object;
}
deep = defaultValue(deep, false);
var isBuffer = Buffer.isBuffer(object);
var isTypedArray = Object.prototype.toString.call(object.buffer) === '[object ArrayBuffer]';
var isArrayStorage = object instanceof ArrayStorage;
var result;
if (isBuffer) {
result = Buffer.from(object);
return result;
} else if (isTypedArray) {
result = object.slice();
return result;
} else if (isArrayStorage) {
result = new ArrayStorage(object.componentDatatype);
} else {
result = new object.constructor();
}
for (var propertyName in object) {
if (object.hasOwnProperty(propertyName)) {
var value = object[propertyName];
if (deep) {
value = clone(value, deep);
}
result[propertyName] = value;
}
}
return result;
}

View File

@ -16,16 +16,6 @@ var DeveloperError = Cesium.DeveloperError;
module.exports = convert; module.exports = convert;
/**
* A callback function that logs messages.
* @callback Logger
*
* @param {String} message The message to log.
*/
var defaultLogger = function(message) {
console.log(message);
};
/** /**
* Converts an obj file to a glTF file. * Converts an obj file to a glTF file.
* *
@ -36,107 +26,196 @@ var defaultLogger = function(message) {
* @param {Boolean} [options.separate=false] Writes out separate geometry data files, shader files, and textures instead of embedding them in the glTF. * @param {Boolean} [options.separate=false] Writes out separate geometry data files, shader files, and textures instead of embedding them in the glTF.
* @param {Boolean} [options.separateTextures=false] Write out separate textures only. * @param {Boolean} [options.separateTextures=false] Write out separate textures only.
* @param {Boolean} [options.compress=false] Quantize positions, compress texture coordinates, and oct-encode normals. * @param {Boolean} [options.compress=false] Quantize positions, compress texture coordinates, and oct-encode normals.
* @param {Boolean} [options.optimize=false] Use the optimization stages in the glTF pipeline. * @param {Boolean} [options.optimize=false] Optimize the glTF for size and runtime performance.
* @param {Boolean} [options.optimizeForCesium=false] Optimize the glTF for Cesium by using the sun as a default light source. * @param {Boolean} [options.optimizeForCesium=false] Optimize the glTF for Cesium by using the sun as a default light source.
* @param {Boolean} [options.generateNormals=false] Generate normals if they are missing. * @param {Boolean} [options.generateNormals=false] Generate normals if they are missing.
* @param {Boolean} [options.ao=false] Apply ambient occlusion to the converted model. * @param {Boolean} [options.ao=false] Apply ambient occlusion to the converted model.
* @param {Boolean} [options.kmc=false] Output glTF with the KHR_materials_common extension. * @param {Boolean} [options.kmc=false] Output glTF with the KHR_materials_common extension.
* @param {Boolean} [options.textureCompressionOptions] Options sent to the compressTextures stage of gltf-pipeline. * @param {Boolean} [options.textureCompressionOptions] Options sent to the compressTextures stage of gltf-pipeline.
* @param {Boolean} [options.bypassPipeline=false] Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension. * @param {Boolean} [options.bypassPipeline=false] Bypass the gltf-pipeline for debugging purposes. This option overrides many of the options above and will save the glTF with the KHR_materials_common extension.
* @param {Boolean} [options.hasTransparency=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. * @param {Boolean} [options.checkTransparency=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel.
* @param {Boolean} [options.secure=false] Prevent the converter from reading image or mtl files outside of the input obj directory. * @param {Boolean} [options.secure=false] Prevent the converter from reading image or mtl files outside of the input obj directory.
* @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.
*/ */
function convert(objPath, gltfPath, options) { function convert(objPath, gltfPath, options) {
return new Promise(function(resolve, reject) { var defaults = convert.defaults;
options = defaultValue(options, {});
var binary = defaultValue(options.binary, false);
var separate = defaultValue(options.separate, false);
var separateTextures = defaultValue(options.separateTextures, false) || separate;
var compress = defaultValue(options.compress, false);
var optimize = defaultValue(options.optimize, false);
var optimizeForCesium = defaultValue(options.optimizeForCesium, false);
var generateNormals = defaultValue(options.generateNormals, false);
var ao = defaultValue(options.ao, false);
var kmc = defaultValue(options.kmc, false);
var textureCompressionOptions = options.textureCompressionOptions;
var bypassPipeline = defaultValue(options.bypassPipeline, false);
var logger = defaultValue(options.logger, defaultLogger);
options.logger = logger;
options.hasTransparency = defaultValue(options.hasTransparency, false);
options.secure = defaultValue(options.secure, false);
if (!defined(objPath)) { options = defaultValue(options, {});
throw new DeveloperError('objPath is required'); var binary = defaultValue(options.binary, defaults.binary);
} var separate = defaultValue(options.separate, defaults.separate);
var separateTextures = defaultValue(options.separateTextures, defaults.separateTextures) || separate;
var compress = defaultValue(options.compress, defaults.compress);
var optimize = defaultValue(options.optimize, defaults.optimize);
var optimizeForCesium = defaultValue(options.optimizeForCesium, defaults.optimizeForCesium);
var generateNormals = defaultValue(options.generateNormals, defaults.generateNormals);
var ao = defaultValue(options.ao, defaults.ao);
var kmc = defaultValue(options.kmc, defaults.kmc);
var textureCompressionOptions = options.textureCompressionOptions;
var bypassPipeline = defaultValue(options.bypassPipeline, defaults.bypassPipeline);
var checkTransparency = defaultValue(options.checkTransparency, defaults.checkTransparency);
var secure = defaultValue(options.secure, defaults.secure);
var logger = defaultValue(options.logger, defaults.logger);
if (!defined(gltfPath)) { options.separate = separate;
throw new DeveloperError('gltfPath is required'); options.separateTextures = separateTextures;
} options.checkTransparency = checkTransparency;
options.secure = secure;
options.logger = logger;
var objExtension = path.extname(objPath).toLowerCase();
if (objExtension !== '.obj') {
throw new DeveloperError('Invalid obj path "' + objPath + '"');
}
var extension = path.extname(gltfPath).toLowerCase(); if (!defined(objPath)) {
if (extension !== '.gltf' && extension !== '.glb') { throw new DeveloperError('objPath is required');
throw new DeveloperError('Invalid gltf path "' + gltfPath + '"'); }
}
var basePath = path.dirname(gltfPath); if (!defined(gltfPath)) {
var modelName = path.basename(gltfPath, path.extname(gltfPath)); throw new DeveloperError('gltfPath is required');
if (extension === '.glb') { }
binary = true;
var extension = path.extname(gltfPath).toLowerCase();
var basePath = path.dirname(gltfPath);
var modelName = path.basename(gltfPath, path.extname(gltfPath));
if (extension === '.glb') {
binary = true;
}
if (binary && bypassPipeline) {
throw new DeveloperError('--bypassPipeline does not convert to binary glTF');
}
gltfPath = path.join(path.dirname(gltfPath), modelName + extension);
var aoOptions = ao ? {} : undefined;
var kmcOptions = kmc ? {} : undefined;
var pipelineOptions = {
createDirectory : false,
basePath : basePath,
binary : binary,
embed : !separate,
embedImage : !separateTextures,
quantize : compress,
compressTextureCoordinates : compress,
encodeNormals : compress,
preserve : !optimize,
optimizeForCesium : optimizeForCesium,
smoothNormals : generateNormals,
aoOptions : aoOptions,
kmcOptions : kmcOptions,
textureCompressionOptions : textureCompressionOptions
};
return loadObj(objPath, options)
.then(function(objData) {
return createGltf(objData);
})
.then(function(gltf) {
return writeUris(gltf, gltfPath, options);
})
.then(function(gltf) {
if (bypassPipeline) { if (bypassPipeline) {
logger('--bypassPipeline does not convert to binary glTF, saving as .gltf'); return convert._outputJson(gltfPath, gltf);
extension = '.gltf'; } else {
return GltfPipeline.processJSONToDisk(gltf, gltfPath, pipelineOptions);
} }
} });
gltfPath = path.join(path.dirname(gltfPath), modelName + extension);
var aoOptions = ao ? {} : undefined;
var kmcOptions = kmc ? {} : undefined;
var pipelineOptions = {
createDirectory : false,
basePath : basePath,
binary : binary,
embed : !separate,
embedImage : !separateTextures,
quantize : compress,
compressTextureCoordinates : compress,
encodeNormals : compress,
preserve : !optimize,
optimizeForCesium : optimizeForCesium,
smoothNormals : generateNormals,
aoOptions : aoOptions,
kmcOptions : kmcOptions,
textureCompressionOptions : textureCompressionOptions
};
return loadObj(objPath, options)
.then(function(objData) {
return createGltf(objData);
})
.then(function(gltf) {
return writeUris(gltf, gltfPath, separate, separateTextures, logger);
})
.then(function(gltf) {
if (bypassPipeline) {
return convert._outputJson(gltfPath, gltf);
} else {
return GltfPipeline.processJSONToDisk(gltf, gltfPath, pipelineOptions);
}
})
.then(resolve)
.catch(reject);
});
} }
/**
* Default values that will be used when calling convert(options) unless specified in the options object.
*/
convert.defaults = {
/**
* Gets or sets whether the model will be saved as binary glTF.
* @type Boolean
* @default false
*/
binary: false,
/**
* Gets or sets whether to write out separate geometry/animation data files,
* shader files, and textures instead of embedding them in the glTF.
* @type Boolean
* @default false
*/
separate: false,
/**
* Gets or sets whether to write out separate textures only.
* @type Boolean
* @default false
*/
separateTextures: false,
/**
* Gets or sets whether to compress attribute data. This includes quantizing positions, compressing texture coordinates, and oct-encoding normals.
* @type Boolean
* @default false
*/
compress: false,
/**
* Gets or sets whether the model is optimized for size and runtime performance.
* @type Boolean
* @default false
*/
optimize: false,
/**
* Gets or sets whether the model is optimized for Cesium by using the sun as a default light source.
* @type Boolean
* @default false
*/
optimizeForCesium: false,
/**
* Gets or sets whether normals will be generated for the model if they are missing.
* @type Boolean
* @default false
*/
generateNormals: false,
/**
* Gets or sets whether the model will have ambient occlusion applied.
* @type Boolean
* @default false
*/
ao: false,
/**
* Gets or sets whether the model will be saved with the KHR_materials_common extension.
* @type Boolean
* @default false
*/
kmc: false,
/**
* Gets or sets whether the converter will bypass the gltf-pipeline for debugging purposes.
* @type Boolean
* @default false
*/
bypassPipeline: false,
/**
* Gets or sets whether the converter will do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel.
* @type Boolean
* @default false
*/
checkTransparency: false,
/**
* Gets or sets whether the source model can reference paths outside of its directory.
* @type Boolean
* @default false
*/
secure: false,
/**
* @private
*/
logger: function(message) {
console.log(message);
}
};
/** /**
* Exposed for testing * Exposed for testing
* *
* @private * @private
*/ */
convert._outputJson = fsExtraOutputJson; convert._outputJson = fsExtraOutputJson;
/**
* A callback function that logs messages.
* @callback Logger
*
* @param {String} message The message to log.
*/

View File

@ -1,6 +1,7 @@
'use strict'; 'use strict';
var Cesium = require('cesium'); var Cesium = require('cesium');
var path = require('path'); var path = require('path');
var Material = require('./Material');
var defined = Cesium.defined; var defined = Cesium.defined;
var defaultValue = Cesium.defaultValue; var defaultValue = Cesium.defaultValue;
@ -67,10 +68,10 @@ function createGltf(objData) {
} }
function createMaterial(material, hasNormals) { function createMaterial(material, hasNormals) {
var ambient = defaultValue(defaultValue(getTextureId(material.ambientColorMap), material.ambientColor), [0, 0, 0, 1]); var ambient = defaultValue(defaultValue(getTextureId(material.ambientTexture), material.ambientColor));
var diffuse = defaultValue(defaultValue(getTextureId(material.diffuseColorMap), material.diffuseColor), [0.5, 0.5, 0.5, 1]); var diffuse = defaultValue(defaultValue(getTextureId(material.diffuseTexture), material.diffuseColor));
var emission = defaultValue(defaultValue(getTextureId(material.emissionColorMap), material.emissionColor), [0, 0, 0, 1]); var emission = defaultValue(defaultValue(getTextureId(material.emissionTexture), material.emissionColor));
var specular = defaultValue(defaultValue(getTextureId(material.specularColorMap), material.specularColor), [0, 0, 0, 1]); var specular = defaultValue(defaultValue(getTextureId(material.specularTexture), material.specularColor));
var alpha = defaultValue(defaultValue(material.alpha), 1.0); var alpha = defaultValue(defaultValue(material.alpha), 1.0);
var shininess = defaultValue(material.specularShininess, 0.0); var shininess = defaultValue(material.specularShininess, 0.0);
var hasSpecular = (shininess > 0.0) && (specular[0] > 0.0 || specular[1] > 0.0 || specular[2] > 0.0); var hasSpecular = (shininess > 0.0) && (specular[0] > 0.0 || specular[1] > 0.0 || specular[2] > 0.0);
@ -79,7 +80,7 @@ function createGltf(objData) {
var transparency = 1.0; var transparency = 1.0;
if (typeof diffuse === 'string') { if (typeof diffuse === 'string') {
transparency = alpha; transparency = alpha;
transparent = images[material.diffuseColorMap].transparent || (transparency < 1.0); transparent = images[material.diffuseTexture].transparent || (transparency < 1.0);
} else { } else {
diffuse[3] = alpha; diffuse[3] = alpha;
transparent = diffuse[3] < 1.0; transparent = diffuse[3] < 1.0;
@ -93,11 +94,6 @@ function createGltf(objData) {
diffuse = [0, 0, 0, 1]; diffuse = [0, 0, 0, 1];
} }
// It's not completely clear whether transparent and doubleSided belong under values or KHR_materials_common
// Put under both for now to handle both situations.
// https://github.com/KhronosGroup/glTF/tree/master/extensions/Khronos/KHR_materials_common
// https://github.com/KhronosGroup/glTF/issues/632
var technique = hasNormals ? (hasSpecular ? 'PHONG' : 'LAMBERT') : 'CONSTANT'; var technique = hasNormals ? (hasSpecular ? 'PHONG' : 'LAMBERT') : 'CONSTANT';
return { return {
extensions : { extensions : {
@ -287,7 +283,8 @@ function createGltf(objData) {
materialId = 'default'; materialId = 'default';
} }
var material = defaultValue(materials[materialId], {}); var material = materials[materialId];
material = defined(material) ? material : new Material();
var gltfMaterial = gltf.materials[materialId]; var gltfMaterial = gltf.materials[materialId];
if (defined(gltfMaterial)) { if (defined(gltfMaterial)) {
// Check if this material has already been added but with incompatible shading // Check if this material has already been added but with incompatible shading

View File

@ -7,7 +7,7 @@ var Promise = require('bluebird');
var fsExtraReadFile = Promise.promisify(fsExtra.readFile); var fsExtraReadFile = Promise.promisify(fsExtra.readFile);
var defaultValue = Cesium.defaultValue; var defined = Cesium.defined;
var WebGLConstants = Cesium.WebGLConstants; var WebGLConstants = Cesium.WebGLConstants;
module.exports = loadImage; module.exports = loadImage;
@ -16,19 +16,16 @@ module.exports = loadImage;
* Load an image file and get information about it. * Load an image file and get information about it.
* *
* @param {String} imagePath Path to the image file. * @param {String} imagePath Path to the image file.
* @param {Object} [options] An object with the following properties: * @param {Object} options An object with the following properties:
* @param {Boolean} [options.hasTransparency=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. * @param {Boolean} options.checkTransparency Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel.
* @returns {Promise} A promise resolving to the image information, or undefined if the file doesn't exist. * @returns {Promise} A promise resolving to the image information, or undefined if the file doesn't exist.
* *
* @private * @private
*/ */
function loadImage(imagePath, options) { function loadImage(imagePath, options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var hasTransparency = defaultValue(options.hasTransparency, false);
return fsExtraReadFile(imagePath) return fsExtraReadFile(imagePath)
.then(function(data) { .then(function(data) {
var extension = path.extname(imagePath); var extension = path.extname(imagePath).toLowerCase();
var info = { var info = {
transparent : false, transparent : false,
@ -38,31 +35,35 @@ function loadImage(imagePath, options) {
}; };
if (extension === '.png') { if (extension === '.png') {
// Color type is encoded in the 25th bit of the png return getPngInfo(data, info, options);
var colorType = data[25];
var channels = getChannels(colorType);
info.format = getFormat(channels);
if (channels === 4) {
info.transparent = true;
if (hasTransparency) {
return isTransparent(data)
.then(function(transparent) {
info.transparent = transparent;
return info;
});
}
}
} }
return info; return info;
}); });
} }
function getPngInfo(data, info, options) {
// Color type is encoded in the 25th bit of the png
var colorType = data[25];
var channels = getChannels(colorType);
info.format = getFormat(channels);
if (channels === 4) {
if (options.checkTransparency) {
return isTransparent(data)
.then(function(transparent) {
info.transparent = transparent;
return info;
});
}
}
return info;
}
function isTransparent(data) { function isTransparent(data) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
new PNG().parse(data, function(error, data) { new PNG().parse(data, function(error, data) {
if (error) { if (defined(error)) {
reject(error); reject(error);
return; return;
} }

View File

@ -1,30 +1,15 @@
'use strict'; 'use strict';
var path = require('path'); var path = require('path');
var Material = require('./Material');
var readLines = require('./readLines'); var readLines = require('./readLines');
module.exports = loadMtl; module.exports = loadMtl;
function Material() {
this.ambientColor = undefined; // Ka
this.emissionColor = undefined; // Ke
this.diffuseColor = undefined; // Kd
this.specularColor = undefined; // Ks
this.specularShininess = undefined; // Ns
this.alpha = undefined; // d / Tr
this.ambientColorMap = undefined; // map_Ka
this.emissionColorMap = undefined; // map_Ke
this.diffuseColorMap = undefined; // map_Kd
this.specularColorMap = undefined; // map_Ks
this.specularShininessMap = undefined; // map_Ns
this.normalMap = undefined; // map_Bump
this.alphaMap = undefined; // map_d
}
/** /**
* Parse an mtl file. * Parse an mtl file.
* *
* @param {String} mtlPath Path to the mtl file. * @param {String} mtlPath Path to the mtl file.
* @returns {Promise} A promise resolving to the materials, or an empty object if the mtl file doesn't exist. * @returns {Promise} A promise resolving to the materials.
* *
* @private * @private
*/ */
@ -32,6 +17,7 @@ function loadMtl(mtlPath) {
var material; var material;
var values; var values;
var value; var value;
var mtlDirectory = path.dirname(mtlPath);
var materials = {}; var materials = {};
function parseLine(line) { function parseLine(line) {
@ -82,19 +68,19 @@ function loadMtl(mtlPath) {
value = line.substring(3).trim(); value = line.substring(3).trim();
material.alpha = parseFloat(value); material.alpha = parseFloat(value);
} else if (/^map_Ka /i.test(line)) { } else if (/^map_Ka /i.test(line)) {
material.ambientColorMap = getAbsolutePath(line.substring(7).trim(), mtlPath); material.ambientTexture = path.resolve(mtlDirectory, line.substring(7).trim());
} else if (/^map_Ke /i.test(line)) { } else if (/^map_Ke /i.test(line)) {
material.emissionColorMap = getAbsolutePath(line.substring(7).trim(), mtlPath); material.emissionTexture = path.resolve(mtlDirectory, line.substring(7).trim());
} else if (/^map_Kd /i.test(line)) { } else if (/^map_Kd /i.test(line)) {
material.diffuseColorMap = getAbsolutePath(line.substring(7).trim(), mtlPath); material.diffuseTexture = path.resolve(mtlDirectory, line.substring(7).trim());
} else if (/^map_Ks /i.test(line)) { } else if (/^map_Ks /i.test(line)) {
material.specularColorMap = getAbsolutePath(line.substring(7).trim(), mtlPath); material.specularTexture = path.resolve(mtlDirectory, line.substring(7).trim());
} else if (/^map_Ns /i.test(line)) { } else if (/^map_Ns /i.test(line)) {
material.specularShininessMap = getAbsolutePath(line.substring(7).trim(), mtlPath); material.specularShininessMap = path.resolve(mtlDirectory, line.substring(7).trim());
} else if (/^map_Bump /i.test(line)) { } else if (/^map_Bump /i.test(line)) {
material.normalMap = getAbsolutePath(line.substring(9).trim(), mtlPath); material.normalMap = path.resolve(mtlDirectory, line.substring(9).trim());
} else if (/^map_d /i.test(line)) { } else if (/^map_d /i.test(line)) {
material.alphaMap = getAbsolutePath(line.substring(6).trim(), mtlPath); material.alphaMap = path.resolve(mtlDirectory, line.substring(6).trim());
} }
} }
@ -103,10 +89,3 @@ function loadMtl(mtlPath) {
return materials; return materials;
}); });
} }
function getAbsolutePath(imagePath, mtlPath) {
if (!path.isAbsolute(imagePath)) {
imagePath = path.join(path.dirname(mtlPath), imagePath);
}
return imagePath;
}

View File

@ -8,7 +8,6 @@ var loadImage = require('./image');
var loadMtl = require('./mtl'); var loadMtl = require('./mtl');
var readLines = require('./readLines'); var readLines = require('./readLines');
var combine = Cesium.combine;
var ComponentDatatype = Cesium.ComponentDatatype; var ComponentDatatype = Cesium.ComponentDatatype;
var defaultValue = Cesium.defaultValue; var defaultValue = Cesium.defaultValue;
var defined = Cesium.defined; var defined = Cesium.defined;
@ -51,25 +50,16 @@ var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(
* Parse an obj file. * Parse an obj file.
* *
* @param {String} objPath Path to the obj file. * @param {String} objPath Path to the obj file.
* @param {Object} [options] An object with the following properties: * @param {Object} options An object with the following properties:
* @param {Boolean} [options.hasTransparency=false] Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. * @param {Boolean} options.checkTransparency Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel.
* @param {Boolean} [options.secure=false] Prevent the converter from reading image or mtl files outside of the input obj directory. * @param {Boolean} options.secure Prevent the converter from reading image or mtl files outside of the input obj directory.
* @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 {Promise} A promise resolving to the obj data. * @returns {Promise} A promise resolving to the obj data.
* @exception {RuntimeError} The file does not have any geometry information in it. * @exception {RuntimeError} The file does not have any geometry information in it.
* *
* @private * @private
*/ */
function loadObj(objPath, options) { function loadObj(objPath, options) {
// The defaults are set in convert as well, this just helps with testing loadObj individually
options = combine(options, {
hasTransparency : false,
secure : false,
logger : function(message) {
console.log(message);
}
});
// Global store of vertex attributes listed in the obj file // Global store of vertex attributes listed in the obj file
var positions = new ArrayStorage(ComponentDatatype.FLOAT); var positions = new ArrayStorage(ComponentDatatype.FLOAT);
var normals = new ArrayStorage(ComponentDatatype.FLOAT); var normals = new ArrayStorage(ComponentDatatype.FLOAT);
@ -293,7 +283,7 @@ function loadObj(objPath, options) {
function finishLoading(nodes, mtlPaths, objPath, options) { function finishLoading(nodes, mtlPaths, objPath, options) {
nodes = cleanNodes(nodes); nodes = cleanNodes(nodes);
if (nodes.length === 0) { if (nodes.length === 0) {
throw new RuntimeError(objPath + ' does not have any geometry data'); return Promise.reject(new RuntimeError(objPath + ' does not have any geometry data'));
} }
return loadMaterials(mtlPaths, objPath, options) return loadMaterials(mtlPaths, objPath, options)
.then(function(materials) { .then(function(materials) {
@ -309,57 +299,50 @@ function finishLoading(nodes, mtlPaths, objPath, options) {
}); });
} }
function getAbsolutePath(mtlPath, objPath) {
if (!path.isAbsolute(mtlPath)) {
mtlPath = path.join(path.dirname(objPath), mtlPath);
}
return mtlPath;
}
function outsideDirectory(filePath, objPath) { function outsideDirectory(filePath, objPath) {
return (path.relative(path.dirname(objPath), filePath).indexOf('..') === 0); return (path.relative(path.dirname(objPath), filePath).indexOf('..') === 0);
} }
function loadMaterials(mtlPaths, objPath, options) { function loadMaterials(mtlPaths, objPath, options) {
var secure = options.secure;
var logger = options.logger;
var objDirectory = path.dirname(objPath);
var materials = {}; var materials = {};
return Promise.map(mtlPaths, function(mtlPath) { return Promise.map(mtlPaths, function(mtlPath) {
mtlPath = getAbsolutePath(mtlPath, objPath); mtlPath = path.resolve(objDirectory, mtlPath);
if (options.secure && outsideDirectory(mtlPath, objPath)) { if (secure && outsideDirectory(mtlPath, objPath)) {
options.logger('Could not read mtl file at ' + mtlPath + ' because it is outside of the obj directory and the secure flag is true. Using default material instead.'); logger('Could not read mtl file at ' + mtlPath + ' because it is outside of the obj directory and the secure flag is true. Using default material instead.');
return; return;
} }
return loadMtl(mtlPath) return loadMtl(mtlPath)
.then(function(materialsInMtl) { .then(function(materialsInMtl) {
materials = combine(materials, materialsInMtl); materials = Object.assign(materials, materialsInMtl);
}) })
.catch(function() { .catch(function() {
options.logger('Could not read mtl file at ' + mtlPath + '. Using default material instead.'); logger('Could not read mtl file at ' + mtlPath + '. Using default material instead.');
}); });
}).then(function() { }, {concurrency : 10})
return materials; .thenReturn(materials);
});
} }
function loadImages(imagePaths, objPath, options) { function loadImages(imagePaths, objPath, options) {
var secure = options.secure;
var logger = options.logger;
var images = {}; var images = {};
return Promise.map(imagePaths, function(imagePath) { return Promise.map(imagePaths, function(imagePath) {
if (options.secure && outsideDirectory(imagePath, objPath)) { if (secure && outsideDirectory(imagePath, objPath)) {
options.logger('Could not read image file at ' + imagePath + ' because it is outside of the obj directory and the secure flag is true. Material will ignore this image.'); logger('Could not read image file at ' + imagePath + ' because it is outside of the obj directory and the secure flag is true. Material will ignore this image.');
return; return;
} }
return loadImage(imagePath, options) return loadImage(imagePath, options)
.then(function(image) { .then(function(image) {
if (defined(image)) { images[imagePath] = image;
images[imagePath] = image;
}
}) })
.catch(function() { .catch(function() {
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.');
return undefined;
}); });
}).then(function() { }, {concurrency : 10})
return images; .thenReturn(images);
});
} }
function getImagePaths(materials) { function getImagePaths(materials) {
@ -367,46 +350,32 @@ function getImagePaths(materials) {
for (var name in materials) { for (var name in materials) {
if (materials.hasOwnProperty(name)) { if (materials.hasOwnProperty(name)) {
var material = materials[name]; var material = materials[name];
if (defined(material.ambientColorMap)) { if (defined(material.ambientTexture)) {
imagePaths[material.ambientColorMap] = true; imagePaths[material.ambientTexture] = true;
} }
if (defined(material.diffuseColorMap)) { if (defined(material.diffuseTexture)) {
imagePaths[material.diffuseColorMap] = true; imagePaths[material.diffuseTexture] = true;
} }
if (defined(material.emissionColorMap)) { if (defined(material.emissionTexture)) {
imagePaths[material.emissionColorMap] = true; imagePaths[material.emissionTexture] = true;
} }
if (defined(material.specularColorMap)) { if (defined(material.specularTexture)) {
imagePaths[material.specularColorMap] = true; imagePaths[material.specularTexture] = true;
} }
} }
} }
return Object.keys(imagePaths); return Object.keys(imagePaths);
} }
function removeEmptyPrimitives(primitives) {
var final = [];
var primitivesLength = primitives.length;
for (var i = 0; i < primitivesLength; ++i) {
var primitive = primitives[i];
if (primitive.indices.length > 0) {
final.push(primitive);
}
}
return final;
}
function removeEmptyMeshes(meshes) { function removeEmptyMeshes(meshes) {
var final = []; return meshes.filter(function(mesh) {
var meshesLength = meshes.length; // Remove empty primitives
for (var i = 0; i < meshesLength; ++i) { mesh.primitives = mesh.primitives.filter(function(primitive) {
var mesh = meshes[i]; return primitive.indices.length > 0;
mesh.primitives = removeEmptyPrimitives(mesh.primitives); });
if ((mesh.primitives.length > 0) && (mesh.positions.length > 0)) { // Valid meshes must have at least one primitive and contain positions
final.push(mesh); return (mesh.primitives.length > 0) && (mesh.positions.length > 0);
} });
}
return final;
} }
function meshesHaveNames(meshes) { function meshesHaveNames(meshes) {

View File

@ -1,4 +1,5 @@
'use strict'; 'use strict';
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');
@ -6,6 +7,8 @@ var Promise = require('bluebird');
var fsExtraOutputFile = Promise.promisify(fsExtra.outputFile); var fsExtraOutputFile = Promise.promisify(fsExtra.outputFile);
var RuntimeError = Cesium.RuntimeError;
module.exports = writeUris; module.exports = writeUris;
/** /**
@ -13,14 +16,17 @@ module.exports = writeUris;
* *
* @param {Object} gltf The glTF asset. * @param {Object} gltf The glTF asset.
* @param {String} gltfPath Path where the glTF will be saved. * @param {String} gltfPath Path where the glTF will be saved.
* @param {Boolean} separateBuffers Writes out separate buffers. * @param {Object} options An object with the following properties:
* @param {Boolean} separateTextures Writes out separate textures. * @param {Boolean} options.separate Writes out separate buffers.
* @param {Logger} logger A callback function for handling logged messages. Defaults to console.log. * @param {Boolean} options.separateTextures Write out separate textures only.
* @returns {Promise} A promise that resolves to the glTF asset. * @returns {Promise} A promise that resolves to the glTF asset.
* *
* @private * @private
*/ */
function writeUris(gltf, gltfPath, separateBuffers, separateTextures, logger) { function writeUris(gltf, gltfPath, options) {
var separate = options.separate;
var separateTextures = options.separateTextures;
var promises = []; var promises = [];
var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]];
@ -37,17 +43,17 @@ function writeUris(gltf, gltfPath, separateBuffers, separateTextures, logger) {
// Buffers larger than ~192MB cannot be base64 encoded due to a NodeJS limitation. Source: https://github.com/nodejs/node/issues/4266 // 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); var exceedsMaximum = (texturesByteLength + bufferByteLength > 201326580);
if (exceedsMaximum) { if (exceedsMaximum && !separate) {
logger('Buffers and textures are too large to encode in the glTF, saving as separate resources.'); return Promise.reject(new RuntimeError('Buffers and textures are too large to encode in the glTF, saving as separate resources.'));
} }
if (separateBuffers || exceedsMaximum) { if (separate) {
promises.push(writeSeparateBuffer(gltf, gltfPath)); promises.push(writeSeparateBuffer(gltf, gltfPath));
} else { } else {
writeEmbeddedBuffer(gltf); writeEmbeddedBuffer(gltf);
} }
if (separateTextures || exceedsMaximum) { if (separateTextures) {
promises.push(writeSeparateTextures(gltf, gltfPath)); promises.push(writeSeparateTextures(gltf, gltfPath));
} else { } else {
writeEmbeddedTextures(gltf); writeEmbeddedTextures(gltf);
@ -84,19 +90,15 @@ function writeSeparateBuffer(gltf, gltfPath) {
} }
function writeSeparateTextures(gltf, gltfPath) { function writeSeparateTextures(gltf, gltfPath) {
var promises = [];
var images = gltf.images; var images = gltf.images;
for (var id in images) { return Promise.map(Object.keys(images), function(id) {
if (images.hasOwnProperty(id)) { var image = images[id];
var image = images[id]; var extras = image.extras._obj2gltf;
var extras = image.extras._obj2gltf; var imageUri = image.name + extras.extension;
var imageUri = image.name + extras.extension; image.uri = imageUri;
image.uri = imageUri; var imagePath = path.join(path.dirname(gltfPath), imageUri);
var imagePath = path.join(path.dirname(gltfPath), imageUri); return writeUris._outputFile(imagePath, extras.source);
promises.push(writeUris._outputFile(imagePath, extras.source)); }, {concurrency : 10});
}
}
return Promise.all(promises);
} }
function writeEmbeddedBuffer(gltf) { function writeEmbeddedBuffer(gltf) {
@ -106,7 +108,6 @@ function writeEmbeddedBuffer(gltf) {
} }
function writeEmbeddedTextures(gltf) { function writeEmbeddedTextures(gltf) {
var promises = [];
var images = gltf.images; var images = gltf.images;
for (var id in images) { for (var id in images) {
if (images.hasOwnProperty(id)) { if (images.hasOwnProperty(id)) {
@ -115,7 +116,6 @@ function writeEmbeddedTextures(gltf) {
image.uri = 'data:' + mime.lookup(extras.extension) + ';base64,' + extras.source.toString('base64'); image.uri = 'data:' + mime.lookup(extras.extension) + ';base64,' + extras.source.toString('base64');
} }
} }
return Promise.all(promises);
} }
/** /**

View File

@ -1,20 +1,13 @@
'use strict'; 'use strict';
var Cesium = require('cesium');
var GltfPipeline = require('gltf-pipeline').Pipeline; var GltfPipeline = require('gltf-pipeline').Pipeline;
var path = require('path'); var path = require('path');
var convert = require('../../lib/convert'); var convert = require('../../lib/convert');
var writeUris = require('../../lib/writeUris'); var writeUris = require('../../lib/writeUris');
var DeveloperError = Cesium.DeveloperError;
var objPath = 'specs/data/box-textured/box-textured.obj'; var objPath = 'specs/data/box-textured/box-textured.obj';
var gltfPath = 'specs/data/box-textured/box-textured.gltf'; var gltfPath = 'specs/data/box-textured/box-textured.gltf';
var glbPath = 'specs/data/box-textured/box-textured.glb'; var glbPath = 'specs/data/box-textured/box-textured.glb';
var objPathInvalid = 'invalid/';
var gltfPathInvalid = 'invalid/model.invalid';
var objPathNonExistent = 'specs/data/non-existent.obj'; var objPathNonExistent = 'specs/data/non-existent.obj';
var gltfPathNonExistent = 'specs/data/non-existent.gltf';
var objExternalResourcesPath = 'specs/data/box-external-resources/box-external-resources.obj'; var objExternalResourcesPath = 'specs/data/box-external-resources/box-external-resources.obj';
describe('convert', function() { describe('convert', function() {
@ -57,8 +50,8 @@ describe('convert', function() {
}); });
it('sets options', function(done) { it('sets options', function(done) {
var spy1 = spyOn(GltfPipeline, 'processJSONToDisk'); var spy = spyOn(GltfPipeline, 'processJSONToDisk');
var spy2 = spyOn(writeUris, '_outputFile'); spyOn(writeUris, '_outputFile');
var textureCompressionOptions = { var textureCompressionOptions = {
format : 'dxt1', format : 'dxt1',
quality : 10 quality : 10
@ -69,16 +62,19 @@ describe('convert', function() {
separateTextures : true, separateTextures : true,
compress : true, compress : true,
optimize : true, optimize : true,
optimizeForCesium : true,
generateNormals : true, generateNormals : true,
ao : true, ao : true,
kmc : true, kmc : true,
optimizeForCesium : true, textureCompressionOptions : textureCompressionOptions,
textureCompressionOptions : textureCompressionOptions checkTransparency : true,
secure : true,
logger : convert.defaults.logger
}; };
expect(convert(objPath, gltfPath, options) expect(convert(objPath, gltfPath, options)
.then(function() { .then(function() {
var args = spy1.calls.first().args; var args = spy.calls.first().args;
var options = args[2]; var options = args[2];
expect(options).toEqual({ expect(options).toEqual({
createDirectory : false, createDirectory : false,
@ -96,7 +92,7 @@ describe('convert', function() {
textureCompressionOptions : textureCompressionOptions, textureCompressionOptions : textureCompressionOptions,
preserve : false preserve : false
}); });
expect(spy2.calls.count()).toBe(2); // Saves out .png and .bin expect(writeUris._outputFile.calls.count()).toBe(2); // Saves out .png and .bin
}), done).toResolve(); }), done).toResolve();
}); });
@ -111,50 +107,31 @@ describe('convert', function() {
}); });
it('bypassPipeline flag bypasses gltf-pipeline', function(done) { it('bypassPipeline flag bypasses gltf-pipeline', function(done) {
var spy1 = spyOn(convert, '_outputJson'); spyOn(convert, '_outputJson');
var spy2 = spyOn(GltfPipeline, 'processJSONToDisk'); spyOn(GltfPipeline, 'processJSONToDisk');
var options = { var options = {
bypassPipeline : true bypassPipeline : true
}; };
expect(convert(objPath, gltfPath, options) expect(convert(objPath, gltfPath, options)
.then(function() { .then(function() {
expect(spy1.calls.count()).toBe(1); expect(convert._outputJson).toHaveBeenCalled();
expect(spy2.calls.count()).toBe(0); expect(GltfPipeline.processJSONToDisk).not.toHaveBeenCalled();
}), done).toResolve(); }), done).toResolve();
}); });
it('uses a custom logger', function(done) {
var spy = spyOn(GltfPipeline, 'processJSONToDisk');
var logCount = 0;
var options = {
secure : true, // Needs to be set to trigger messages
logger : function() {
logCount++;
}
};
expect(convert(objExternalResourcesPath, gltfPath, options)
.then(function() {
expect(logCount).toEqual(2);
}), done).toResolve();
});
it('rejects if objPath is undefined', function(done) {
expect(convert(undefined, gltfPath), done).toRejectWith(DeveloperError);
});
it('rejects if gltfPath is undefined', function(done) {
expect(convert(objPath, undefined), done).toRejectWith(DeveloperError);
});
it('rejects if obj path is invalid', function(done) {
expect(convert(objPathInvalid, gltfPath), done).toRejectWith(DeveloperError);
});
it('rejects if gltf path is invalid', function(done) {
expect(convert(objPath, gltfPathInvalid), done).toRejectWith(DeveloperError);
});
it('rejects if obj path does not exist', function(done) { it('rejects if obj path does not exist', function(done) {
expect(convert(objPathNonExistent, gltfPath), done).toRejectWith(Error); expect(convert(objPathNonExistent, gltfPath), done).toRejectWith(Error);
}); });
it('throws if objPath is undefined', function() {
expect(function() {
convert(undefined, gltfPath);
}).toThrowDeveloperError();
});
it('rejects if gltfPath is undefined', function() {
expect(function() {
convert(objPath, undefined);
}).toThrowDeveloperError();
});
}); });

View File

@ -3,12 +3,14 @@ var Cesium = require('cesium');
var fsExtra = require('fs-extra'); var fsExtra = require('fs-extra');
var path = require('path'); var path = require('path');
var Promise = require('bluebird'); var Promise = require('bluebird');
var clone = require('../../lib/clone.js'); var convert = require('../../lib/convert');
var createGltf = require('../../lib/gltf.js'); var createGltf = require('../../lib/gltf');
var loadImage = require('../../lib/image.js'); var loadImage = require('../../lib/image');
var loadObj = require('../../lib/obj.js'); var loadObj = require('../../lib/obj');
var writeUris = require('../../lib/writeUris.js'); var Material = require('../../lib/Material');
var writeUris = require('../../lib/writeUris');
var clone = Cesium.clone;
var WebGLConstants = Cesium.WebGLConstants; var WebGLConstants = Cesium.WebGLConstants;
var fsExtraReadJson = Promise.promisify(fsExtra.readJson); var fsExtraReadJson = Promise.promisify(fsExtra.readJson);
@ -20,21 +22,30 @@ var groupGltfUrl = 'specs/data/box-objects-groups-materials/box-objects-groups-m
var diffuseTextureUrl = 'specs/data/box-textured/cesium.png'; var diffuseTextureUrl = 'specs/data/box-textured/cesium.png';
var transparentDiffuseTextureUrl = 'specs/data/box-complex-material/diffuse.png'; var transparentDiffuseTextureUrl = 'specs/data/box-complex-material/diffuse.png';
var defaultOptions = convert.defaults;
var checkTransparencyOptions = clone(defaultOptions);
checkTransparencyOptions.checkTransparency = true;
describe('gltf', function() { describe('gltf', function() {
var boxObjData; var boxObjData;
var duplicateBoxObjData;
var groupObjData; var groupObjData;
var boxGltf; var boxGltf;
var groupGltf; var groupGltf;
var diffuseTexture; var diffuseTexture;
var transparentDiffuseTexture; var transparentDiffuseTexture;
beforeAll(function(done) { beforeEach(function(done) {
return Promise.all([ return Promise.all([
loadObj(boxObjUrl) loadObj(boxObjUrl, defaultOptions)
.then(function(data) { .then(function(data) {
boxObjData = data; boxObjData = data;
}), }),
loadObj(groupObjUrl) loadObj(boxObjUrl, defaultOptions)
.then(function(data) {
duplicateBoxObjData = data;
}),
loadObj(groupObjUrl, defaultOptions)
.then(function(data) { .then(function(data) {
groupObjData = data; groupObjData = data;
}), }),
@ -46,11 +57,11 @@ describe('gltf', function() {
.then(function(gltf) { .then(function(gltf) {
groupGltf = gltf; groupGltf = gltf;
}), }),
loadImage(diffuseTextureUrl) loadImage(diffuseTextureUrl, defaultOptions)
.then(function(image) { .then(function(image) {
diffuseTexture = image; diffuseTexture = image;
}), }),
loadImage(transparentDiffuseTextureUrl) loadImage(transparentDiffuseTextureUrl, checkTransparencyOptions)
.then(function(image) { .then(function(image) {
transparentDiffuseTexture = image; transparentDiffuseTexture = image;
}) })
@ -58,19 +69,17 @@ describe('gltf', function() {
}); });
it('simple gltf', function(done) { it('simple gltf', function(done) {
var objData = clone(boxObjData, true); var gltf = createGltf(boxObjData);
var gltf = createGltf(objData); expect(writeUris(gltf, boxGltfUrl, defaultOptions)
expect(writeUris(gltf, boxGltfUrl, false, false)
.then(function() { .then(function() {
expect(gltf).toEqual(boxGltf); expect(gltf).toEqual(boxGltf);
}), done).toResolve(); }), done).toResolve();
}); });
it('multiple nodes, meshes, and primitives', function(done) { it('multiple nodes, meshes, and primitives', function(done) {
var objData = clone(groupObjData, true); var gltf = createGltf(groupObjData);
var gltf = createGltf(objData);
expect(writeUris(gltf, groupGltfUrl, false, false) expect(writeUris(gltf, groupGltfUrl, defaultOptions)
.then(function() { .then(function() {
expect(gltf).toEqual(groupGltf); expect(gltf).toEqual(groupGltf);
expect(Object.keys(gltf.materials).length).toBe(3); expect(Object.keys(gltf.materials).length).toBe(3);
@ -88,10 +97,9 @@ describe('gltf', function() {
}); });
it('sets default material values', function() { it('sets default material values', function() {
var objData = clone(boxObjData, true); boxObjData.materials.Material = new Material();
objData.materials.Material = {};
var gltf = createGltf(objData); var gltf = createGltf(boxObjData);
var material = gltf.materials.Material; var material = gltf.materials.Material;
var kmc = material.extensions.KHR_materials_common; var kmc = material.extensions.KHR_materials_common;
var values = kmc.values; var values = kmc.values;
@ -105,13 +113,12 @@ describe('gltf', function() {
}); });
it('sets material for diffuse texture', function() { it('sets material for diffuse texture', function() {
var objData = clone(boxObjData, true); var material = new Material();
objData.materials.Material = { material.diffuseTexture = diffuseTextureUrl;
diffuseColorMap : diffuseTextureUrl boxObjData.materials.Material = material;
}; boxObjData.images[diffuseTextureUrl] = diffuseTexture;
objData.images[diffuseTextureUrl] = diffuseTexture;
var gltf = createGltf(objData); var gltf = createGltf(boxObjData);
var kmc = gltf.materials.Material.extensions.KHR_materials_common; var kmc = gltf.materials.Material.extensions.KHR_materials_common;
var texture = gltf.textures.texture_cesium; var texture = gltf.textures.texture_cesium;
var image = gltf.images.cesium; var image = gltf.images.cesium;
@ -145,12 +152,11 @@ describe('gltf', function() {
}); });
it('sets material for alpha less than 1', function() { it('sets material for alpha less than 1', function() {
var objData = clone(boxObjData, true); var material = new Material();
objData.materials.Material = { material.alpha = 0.4;
alpha : 0.4 boxObjData.materials.Material = material;
};
var gltf = createGltf(objData); var gltf = createGltf(boxObjData);
var kmc = gltf.materials.Material.extensions.KHR_materials_common; var kmc = gltf.materials.Material.extensions.KHR_materials_common;
expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 0.4]); expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 0.4]);
@ -160,14 +166,14 @@ describe('gltf', function() {
}); });
it('sets material for diffuse texture and alpha less than 1', function() { it('sets material for diffuse texture and alpha less than 1', function() {
var objData = clone(boxObjData, true); var material = new Material();
objData.materials.Material = { material.diffuseTexture = diffuseTextureUrl;
diffuseColorMap : diffuseTextureUrl, material.alpha = 0.4;
alpha : 0.4 boxObjData.materials.Material = material;
};
objData.images[diffuseTextureUrl] = diffuseTexture;
var gltf = createGltf(objData); boxObjData.images[diffuseTextureUrl] = diffuseTexture;
var gltf = createGltf(boxObjData);
var kmc = gltf.materials.Material.extensions.KHR_materials_common; var kmc = gltf.materials.Material.extensions.KHR_materials_common;
expect(kmc.values.diffuse).toEqual('texture_cesium'); expect(kmc.values.diffuse).toEqual('texture_cesium');
@ -177,13 +183,13 @@ describe('gltf', function() {
}); });
it('sets material for transparent diffuse texture', function() { it('sets material for transparent diffuse texture', function() {
var objData = clone(boxObjData, true); var material = new Material();
objData.materials.Material = { material.diffuseTexture = transparentDiffuseTextureUrl;
diffuseColorMap : transparentDiffuseTextureUrl boxObjData.materials.Material = material;
};
objData.images[transparentDiffuseTextureUrl] = transparentDiffuseTexture;
var gltf = createGltf(objData); boxObjData.images[transparentDiffuseTextureUrl] = transparentDiffuseTexture;
var gltf = createGltf(boxObjData);
var kmc = gltf.materials.Material.extensions.KHR_materials_common; var kmc = gltf.materials.Material.extensions.KHR_materials_common;
expect(kmc.values.diffuse).toBe('texture_diffuse'); expect(kmc.values.diffuse).toBe('texture_diffuse');
@ -193,13 +199,12 @@ describe('gltf', function() {
}); });
it('sets material for specular', function() { it('sets material for specular', function() {
var objData = clone(boxObjData, true); var material = new Material();
objData.materials.Material = { material.specularColor = [0.1, 0.1, 0.2, 1];
specularColor : [0.1, 0.1, 0.2, 1], material.specularShininess = 0.1;
specularShininess : 0.1 boxObjData.materials.Material = material;
};
var gltf = createGltf(objData); var gltf = createGltf(boxObjData);
var kmc = gltf.materials.Material.extensions.KHR_materials_common; var kmc = gltf.materials.Material.extensions.KHR_materials_common;
expect(kmc.technique).toBe('PHONG'); expect(kmc.technique).toBe('PHONG');
@ -208,14 +213,15 @@ describe('gltf', function() {
}); });
it('sets constant material when there are no normals', function() { it('sets constant material when there are no normals', function() {
var objData = clone(boxObjData, true); boxObjData.nodes[0].meshes[0].normals.length = 0;
objData.nodes[0].meshes[0].normals.length = 0;
objData.materials.Material = {
diffuseColorMap : diffuseTextureUrl
};
objData.images[diffuseTextureUrl] = diffuseTexture;
var gltf = createGltf(objData); var material = new Material();
material.diffuseTexture = diffuseTextureUrl;
boxObjData.materials.Material = material;
boxObjData.images[diffuseTextureUrl] = diffuseTexture;
var gltf = createGltf(boxObjData);
var kmc = gltf.materials.Material.extensions.KHR_materials_common; var kmc = gltf.materials.Material.extensions.KHR_materials_common;
expect(kmc.technique).toBe('CONSTANT'); expect(kmc.technique).toBe('CONSTANT');
@ -223,70 +229,66 @@ describe('gltf', function() {
}); });
it('sets default material when texture is missing', function() { it('sets default material when texture is missing', function() {
var objData = clone(boxObjData, true); var material = new Material();
objData.materials.Material = { material.diffuseTexture = diffuseTextureUrl;
diffuseColorMap : diffuseTextureUrl boxObjData.materials.Material = material;
};
var gltf = createGltf(objData); var gltf = createGltf(boxObjData);
var kmc = gltf.materials.Material.extensions.KHR_materials_common; var kmc = gltf.materials.Material.extensions.KHR_materials_common;
expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 1.0]); expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 1.0]);
}); });
it('uses default material (1)', function() { it('uses default material (1)', function() {
var objData = clone(boxObjData, true); boxObjData.nodes[0].meshes[0].primitives[0].material = undefined;
objData.nodes[0].meshes[0].primitives[0].material = undefined;
// Creates a material called "default" // Creates a material called "default"
var gltf = createGltf(objData); var gltf = createGltf(boxObjData);
expect(gltf.materials.default).toBeDefined(); expect(gltf.materials.default).toBeDefined();
var kmc = gltf.materials.default.extensions.KHR_materials_common; var kmc = gltf.materials.default.extensions.KHR_materials_common;
expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 1.0]); expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 1.0]);
}); });
it('uses default material (2)', function() { it('uses default material (2)', function() {
var objData = clone(boxObjData, true); boxObjData.materials = {};
objData.materials = {};
// Uses the original name of the material // Uses the original name of the material
var gltf = createGltf(objData); var gltf = createGltf(boxObjData);
var kmc = gltf.materials.Material.extensions.KHR_materials_common; var kmc = gltf.materials.Material.extensions.KHR_materials_common;
expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 1.0]); expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 1.0]);
}); });
it('handles material used with and without normals', function() { it('handles material used with and without normals (1)', function() {
// Two meshes - one with normals, and one without // Two meshes - one with normals, and one without
var objData = clone(boxObjData, true); boxObjData.nodes.push(duplicateBoxObjData.nodes[0]);
objData.nodes.push(clone(objData.nodes[0], true)); boxObjData.nodes[1].meshes[0].normals.length = 0;
objData.nodes[1].meshes[0].normals.length = 0;
var gltf = createGltf(objData); var gltf = createGltf(boxObjData);
var kmc1 = gltf.materials.Material.extensions.KHR_materials_common; var kmc1 = gltf.materials.Material.extensions.KHR_materials_common;
var kmc2 = gltf.materials.Material_constant.extensions.KHR_materials_common; var kmc2 = gltf.materials.Material_constant.extensions.KHR_materials_common;
expect(kmc1.technique).toBe('PHONG'); expect(kmc1.technique).toBe('PHONG');
expect(kmc2.technique).toBe('CONSTANT'); expect(kmc2.technique).toBe('CONSTANT');
});
it('handles material used with and without normals (2)', function() {
// Now test in a different order // Now test in a different order
objData = clone(boxObjData, true); boxObjData.nodes.push(duplicateBoxObjData.nodes[0]);
objData.nodes.push(clone(objData.nodes[0], true)); boxObjData.nodes[0].meshes[0].normals.length = 0;
objData.nodes[0].meshes[0].normals.length = 0;
gltf = createGltf(objData); var gltf = createGltf(boxObjData);
kmc1 = gltf.materials.Material.extensions.KHR_materials_common; var kmc1 = gltf.materials.Material.extensions.KHR_materials_common;
kmc2 = gltf.materials.Material_shaded.extensions.KHR_materials_common; var kmc2 = gltf.materials.Material_shaded.extensions.KHR_materials_common;
expect(kmc1.technique).toBe('CONSTANT'); expect(kmc1.technique).toBe('CONSTANT');
expect(kmc2.technique).toBe('PHONG'); expect(kmc2.technique).toBe('PHONG');
}); });
it('runs without normals', function() { it('runs without normals', function() {
var objData = clone(boxObjData, true); boxObjData.nodes[0].meshes[0].normals.length = 0;
objData.nodes[0].meshes[0].normals.length = 0;
var gltf = createGltf(objData); var gltf = createGltf(boxObjData);
var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes; var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes;
expect(attributes.POSITION).toBeDefined(); expect(attributes.POSITION).toBeDefined();
expect(attributes.NORMAL).toBeUndefined(); expect(attributes.NORMAL).toBeUndefined();
@ -294,10 +296,9 @@ describe('gltf', function() {
}); });
it('runs without uvs', function() { it('runs without uvs', function() {
var objData = clone(boxObjData, true); boxObjData.nodes[0].meshes[0].uvs.length = 0;
objData.nodes[0].meshes[0].uvs.length = 0;
var gltf = createGltf(objData); var gltf = createGltf(boxObjData);
var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes; var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes;
expect(attributes.POSITION).toBeDefined(); expect(attributes.POSITION).toBeDefined();
expect(attributes.NORMAL).toBeDefined(); expect(attributes.NORMAL).toBeDefined();
@ -305,11 +306,10 @@ describe('gltf', function() {
}); });
it('runs without uvs and normals', function() { it('runs without uvs and normals', function() {
var objData = clone(boxObjData, true); boxObjData.nodes[0].meshes[0].normals.length = 0;
objData.nodes[0].meshes[0].normals.length = 0; boxObjData.nodes[0].meshes[0].uvs.length = 0;
objData.nodes[0].meshes[0].uvs.length = 0;
var gltf = createGltf(objData); var gltf = createGltf(boxObjData);
var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes; var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes;
expect(attributes.POSITION).toBeDefined(); expect(attributes.POSITION).toBeDefined();
expect(attributes.NORMAL).toBeUndefined(); expect(attributes.NORMAL).toBeUndefined();
@ -344,13 +344,12 @@ describe('gltf', function() {
} }
it('detects need to use uint32 indices', function() { it('detects need to use uint32 indices', function() {
var objData = clone(boxObjData, true); expandObjData(boxObjData, 2731); // Right above 65536 limit
expandObjData(objData, 2731); // Right above 65536 limit var mesh = boxObjData.nodes[0].meshes[0];
var mesh = objData.nodes[0].meshes[0];
var indicesLength = mesh.primitives[0].indices.length; var indicesLength = mesh.primitives[0].indices.length;
var vertexCount = mesh.positions.length / 3; var vertexCount = mesh.positions.length / 3;
var gltf = createGltf(objData); var gltf = createGltf(boxObjData);
var primitive = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0]; var primitive = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0];
var indicesAccessor = gltf.accessors[primitive.indices]; var indicesAccessor = gltf.accessors[primitive.indices];
expect(indicesAccessor.count).toBe(indicesLength); expect(indicesAccessor.count).toBe(indicesLength);

View File

@ -1,8 +1,9 @@
'use strict'; 'use strict';
var Cesium = require('cesium'); var Cesium = require('cesium');
var path = require('path'); var convert = require('../../lib/convert');
var loadImage = require('../../lib/image.js'); var loadImage = require('../../lib/image');
var clone = Cesium.clone;
var WebGLConstants = Cesium.WebGLConstants; var WebGLConstants = Cesium.WebGLConstants;
var pngImage = 'specs/data/box-complex-material/shininess.png'; var pngImage = 'specs/data/box-complex-material/shininess.png';
@ -11,12 +12,12 @@ var jpegImage = 'specs/data/box-complex-material/specular.jpeg';
var gifImage = 'specs/data/box-complex-material/ambient.gif'; var gifImage = 'specs/data/box-complex-material/ambient.gif';
var grayscaleImage = 'specs/data/box-complex-material/alpha.png'; var grayscaleImage = 'specs/data/box-complex-material/alpha.png';
var transparentImage = 'specs/data/box-complex-material/diffuse.png'; var transparentImage = 'specs/data/box-complex-material/diffuse.png';
var opaqueAlphaImage = 'specs/data/box-complex-material/bump.png';
var invalidImage = 'invalid.png'; var defaultOptions = convert.defaults;
describe('image', function() { describe('image', function() {
it('loads png image', function(done) { it('loads png image', function(done) {
expect(loadImage(pngImage) expect(loadImage(pngImage, defaultOptions)
.then(function(info) { .then(function(info) {
expect(info.transparent).toBe(false); expect(info.transparent).toBe(false);
expect(info.format).toBe(WebGLConstants.RGB); expect(info.format).toBe(WebGLConstants.RGB);
@ -26,7 +27,7 @@ describe('image', function() {
}); });
it('loads jpg image', function(done) { it('loads jpg image', function(done) {
expect(loadImage(jpgImage) expect(loadImage(jpgImage, defaultOptions)
.then(function(info) { .then(function(info) {
expect(info.transparent).toBe(false); expect(info.transparent).toBe(false);
expect(info.format).toBe(WebGLConstants.RGB); expect(info.format).toBe(WebGLConstants.RGB);
@ -36,7 +37,7 @@ describe('image', function() {
}); });
it('loads jpeg image', function(done) { it('loads jpeg image', function(done) {
expect(loadImage(jpegImage) expect(loadImage(jpegImage, defaultOptions)
.then(function(info) { .then(function(info) {
expect(info.transparent).toBe(false); expect(info.transparent).toBe(false);
expect(info.format).toBe(WebGLConstants.RGB); expect(info.format).toBe(WebGLConstants.RGB);
@ -46,7 +47,7 @@ describe('image', function() {
}); });
it('loads gif image', function(done) { it('loads gif image', function(done) {
expect(loadImage(gifImage) expect(loadImage(gifImage, defaultOptions)
.then(function(info) { .then(function(info) {
expect(info.transparent).toBe(false); expect(info.transparent).toBe(false);
expect(info.format).toBe(WebGLConstants.RGB); expect(info.format).toBe(WebGLConstants.RGB);
@ -56,7 +57,7 @@ describe('image', function() {
}); });
it('loads grayscale image', function(done) { it('loads grayscale image', function(done) {
expect(loadImage(grayscaleImage) expect(loadImage(grayscaleImage, defaultOptions)
.then(function(info) { .then(function(info) {
expect(info.transparent).toBe(false); expect(info.transparent).toBe(false);
expect(info.format).toBe(WebGLConstants.ALPHA); expect(info.format).toBe(WebGLConstants.ALPHA);
@ -65,30 +66,20 @@ describe('image', function() {
}), done).toResolve(); }), done).toResolve();
}); });
it('loads transparent image', function(done) { it('loads image with alpha channel', function(done) {
expect(loadImage(transparentImage) expect(loadImage(transparentImage, defaultOptions)
.then(function(info) {
expect(info.transparent).toBe(true);
expect(info.format).toBe(WebGLConstants.RGBA);
expect(info.source).toBeDefined();
expect(info.extension).toBe('.png');
}), done).toResolve();
});
it('loads image with fully opaque alpha channel', function(done) {
expect(loadImage(opaqueAlphaImage)
.then(function(info) {
expect(info.transparent).toBe(true);
}), done).toResolve();
});
it('loads image with fully opaque alpha channel with hasTransparency flag', function(done) {
var options = {
hasTransparency : true
};
expect(loadImage(opaqueAlphaImage, options)
.then(function(info) { .then(function(info) {
expect(info.transparent).toBe(false); expect(info.transparent).toBe(false);
}), done).toResolve(); }), done).toResolve();
}); });
it('loads image with checkTransparency flag', function(done) {
var options = clone(defaultOptions);
options.checkTransparency = true;
expect(loadImage(transparentImage, options)
.then(function(info) {
expect(info.transparent).toBe(true);
}), done).toResolve();
});
}); });

View File

@ -1,13 +1,12 @@
'use strict'; 'use strict';
var path = require('path'); var path = require('path');
var loadMtl = require('../../lib/mtl.js'); var loadMtl = require('../../lib/mtl');
var complexMaterialUrl = 'specs/data/box-complex-material/box-complex-material.mtl'; var complexMaterialUrl = 'specs/data/box-complex-material/box-complex-material.mtl';
var multipleMaterialsUrl = 'specs/data/box-multiple-materials/box-multiple-materials.mtl'; var multipleMaterialsUrl = 'specs/data/box-multiple-materials/box-multiple-materials.mtl';
var invalidMaterialUrl = 'invalid.mtl';
function getImagePath(objPath, relativePath) { function getImagePath(objPath, relativePath) {
return path.normalize(path.join(path.dirname(objPath), relativePath)); return path.resolve(path.dirname(objPath), relativePath);
} }
describe('mtl', function() { describe('mtl', function() {
@ -22,10 +21,10 @@ describe('mtl', function() {
expect(material.specularColor).toEqual([0.5, 0.5, 0.5, 1.0]); expect(material.specularColor).toEqual([0.5, 0.5, 0.5, 1.0]);
expect(material.specularShininess).toEqual(96.078431); expect(material.specularShininess).toEqual(96.078431);
expect(material.alpha).toEqual(0.9); expect(material.alpha).toEqual(0.9);
expect(material.ambientColorMap).toEqual(getImagePath(complexMaterialUrl, 'ambient.gif')); expect(material.ambientTexture).toEqual(getImagePath(complexMaterialUrl, 'ambient.gif'));
expect(material.emissionColorMap).toEqual(getImagePath(complexMaterialUrl, 'emission.jpg')); expect(material.emissionTexture).toEqual(getImagePath(complexMaterialUrl, 'emission.jpg'));
expect(material.diffuseColorMap).toEqual(getImagePath(complexMaterialUrl, 'diffuse.png')); expect(material.diffuseTexture).toEqual(getImagePath(complexMaterialUrl, 'diffuse.png'));
expect(material.specularColorMap).toEqual(getImagePath(complexMaterialUrl, 'specular.jpeg')); expect(material.specularTexture).toEqual(getImagePath(complexMaterialUrl, 'specular.jpeg'));
expect(material.specularShininessMap).toEqual(getImagePath(complexMaterialUrl, 'shininess.png')); expect(material.specularShininessMap).toEqual(getImagePath(complexMaterialUrl, 'shininess.png'));
expect(material.normalMap).toEqual(getImagePath(complexMaterialUrl, 'bump.png')); expect(material.normalMap).toEqual(getImagePath(complexMaterialUrl, 'bump.png'));
expect(material.alphaMap).toEqual(getImagePath(complexMaterialUrl, 'alpha.png')); expect(material.alphaMap).toEqual(getImagePath(complexMaterialUrl, 'alpha.png'));

View File

@ -2,8 +2,10 @@
var Cesium = require('cesium'); var Cesium = require('cesium');
var path = require('path'); var path = require('path');
var Promise = require('bluebird'); var Promise = require('bluebird');
var loadObj = require('../../lib/obj.js'); var convert = require('../../lib/convert');
var loadObj = require('../../lib/obj');
var clone = Cesium.clone;
var RuntimeError = Cesium.RuntimeError; var RuntimeError = Cesium.RuntimeError;
var objUrl = 'specs/data/box/box.obj'; var objUrl = 'specs/data/box/box.obj';
@ -54,12 +56,14 @@ function getPrimitives(data) {
} }
function getImagePath(objPath, relativePath) { function getImagePath(objPath, relativePath) {
return path.normalize(path.join(path.dirname(objPath), relativePath)); return path.resolve(path.dirname(objPath), relativePath);
} }
var defaultOptions = convert.defaults;
describe('obj', function() { describe('obj', function() {
it('loads obj with positions, normals, and uvs', function(done) { it('loads obj with positions, normals, and uvs', function(done) {
expect(loadObj(objUrl) expect(loadObj(objUrl, defaultOptions)
.then(function(data) { .then(function(data) {
var images = data.images; var images = data.images;
var materials = data.materials; var materials = data.materials;
@ -88,7 +92,7 @@ describe('obj', function() {
}); });
it('loads obj with normals', function(done) { it('loads obj with normals', function(done) {
expect(loadObj(objNormalsUrl) expect(loadObj(objNormalsUrl, defaultOptions)
.then(function(data) { .then(function(data) {
var mesh = getMeshes(data)[0]; var mesh = getMeshes(data)[0];
expect(mesh.positions.length / 3).toBe(24); expect(mesh.positions.length / 3).toBe(24);
@ -98,7 +102,7 @@ describe('obj', function() {
}); });
it('loads obj with uvs', function(done) { it('loads obj with uvs', function(done) {
expect(loadObj(objUvsUrl) expect(loadObj(objUvsUrl, defaultOptions)
.then(function(data) { .then(function(data) {
var mesh = getMeshes(data)[0]; var mesh = getMeshes(data)[0];
expect(mesh.positions.length / 3).toBe(20); expect(mesh.positions.length / 3).toBe(20);
@ -109,8 +113,8 @@ describe('obj', function() {
it('loads obj with negative indices', function(done) { it('loads obj with negative indices', function(done) {
expect(Promise.all([ expect(Promise.all([
loadObj(objPositionsOnlyUrl), loadObj(objPositionsOnlyUrl, defaultOptions),
loadObj(objNegativeIndicesUrl) loadObj(objNegativeIndicesUrl, defaultOptions)
]) ])
.then(function(results) { .then(function(results) {
var positionsReference = getMeshes(results[0])[0].positions.toFloatBuffer(); var positionsReference = getMeshes(results[0])[0].positions.toFloatBuffer();
@ -120,7 +124,7 @@ describe('obj', function() {
}); });
it('loads obj with triangle faces', function(done) { it('loads obj with triangle faces', function(done) {
expect(loadObj(objTrianglesUrl) expect(loadObj(objTrianglesUrl, defaultOptions)
.then(function(data) { .then(function(data) {
var mesh = getMeshes(data)[0]; var mesh = getMeshes(data)[0];
var primitive = getPrimitives(data)[0]; var primitive = getPrimitives(data)[0];
@ -130,7 +134,7 @@ describe('obj', function() {
}); });
it('loads obj with objects', function(done) { it('loads obj with objects', function(done) {
expect(loadObj(objObjectsUrl) expect(loadObj(objObjectsUrl, defaultOptions)
.then(function(data) { .then(function(data) {
var nodes = data.nodes; var nodes = data.nodes;
expect(nodes.length).toBe(3); expect(nodes.length).toBe(3);
@ -147,7 +151,7 @@ describe('obj', function() {
}); });
it('loads obj with groups', function(done) { it('loads obj with groups', function(done) {
expect(loadObj(objGroupsUrl) expect(loadObj(objGroupsUrl, defaultOptions)
.then(function(data) { .then(function(data) {
var nodes = data.nodes; var nodes = data.nodes;
expect(nodes.length).toBe(3); expect(nodes.length).toBe(3);
@ -164,7 +168,7 @@ describe('obj', function() {
}); });
it('loads obj with objects and groups', function(done) { it('loads obj with objects and groups', function(done) {
expect(loadObj(objObjectsGroupsUrl) expect(loadObj(objObjectsGroupsUrl, defaultOptions)
.then(function(data) { .then(function(data) {
var nodes = data.nodes; var nodes = data.nodes;
expect(nodes.length).toBe(3); expect(nodes.length).toBe(3);
@ -187,7 +191,7 @@ describe('obj', function() {
}); });
it('loads obj with usemtl only', function(done) { it('loads obj with usemtl only', function(done) {
expect(loadObj(objUsemtlUrl) expect(loadObj(objUsemtlUrl, defaultOptions)
.then(function(data) { .then(function(data) {
var nodes = data.nodes; var nodes = data.nodes;
expect(nodes.length).toBe(1); expect(nodes.length).toBe(1);
@ -206,7 +210,7 @@ describe('obj', function() {
}); });
it('loads obj with no materials', function(done) { it('loads obj with no materials', function(done) {
expect(loadObj(objNoMaterialsUrl) expect(loadObj(objNoMaterialsUrl, defaultOptions)
.then(function(data) { .then(function(data) {
var nodes = data.nodes; var nodes = data.nodes;
expect(nodes.length).toBe(1); expect(nodes.length).toBe(1);
@ -219,7 +223,7 @@ describe('obj', function() {
it('loads obj with multiple materials', function(done) { it('loads obj with multiple materials', function(done) {
// The usemtl markers are interleaved, but should condense to just three primitives // The usemtl markers are interleaved, but should condense to just three primitives
expect(loadObj(objMultipleMaterialsUrl) expect(loadObj(objMultipleMaterialsUrl, defaultOptions)
.then(function(data) { .then(function(data) {
var nodes = data.nodes; var nodes = data.nodes;
expect(nodes.length).toBe(1); expect(nodes.length).toBe(1);
@ -239,7 +243,7 @@ describe('obj', function() {
it('loads obj uncleaned', function(done) { it('loads obj uncleaned', function(done) {
// Obj with extraneous o, g, and usemtl lines // Obj with extraneous o, g, and usemtl lines
// Also tests handling of o and g lines with the same names // Also tests handling of o and g lines with the same names
expect(loadObj(objUncleanedUrl) expect(loadObj(objUncleanedUrl, defaultOptions)
.then(function(data) { .then(function(data) {
var nodes = data.nodes; var nodes = data.nodes;
var meshes = getMeshes(data); var meshes = getMeshes(data);
@ -255,7 +259,7 @@ describe('obj', function() {
}); });
it('loads obj with multiple mtllibs', function(done) { it('loads obj with multiple mtllibs', function(done) {
expect(loadObj(objMtllibUrl) expect(loadObj(objMtllibUrl, defaultOptions)
.then(function(data) { .then(function(data) {
var materials = data.materials; var materials = data.materials;
expect(Object.keys(materials).length).toBe(3); expect(Object.keys(materials).length).toBe(3);
@ -267,7 +271,7 @@ describe('obj', function() {
it('loads obj with missing mtllib', function(done) { it('loads obj with missing mtllib', function(done) {
spyOn(console, 'log'); spyOn(console, 'log');
expect(loadObj(objMissingMtllibUrl) expect(loadObj(objMissingMtllibUrl, defaultOptions)
.then(function(data) { .then(function(data) {
expect(data.materials).toEqual({}); expect(data.materials).toEqual({});
expect(console.log.calls.argsFor(0)[0].indexOf('Could not read mtl file') >= 0).toBe(true); expect(console.log.calls.argsFor(0)[0].indexOf('Could not read mtl file') >= 0).toBe(true);
@ -275,19 +279,20 @@ describe('obj', function() {
}); });
it('loads resources outside of the obj directory', function(done) { it('loads resources outside of the obj directory', function(done) {
expect(loadObj(objExternalResourcesUrl) expect(loadObj(objExternalResourcesUrl, defaultOptions)
.then(function(data) { .then(function(data) {
var imagePath = getImagePath(objTexturedUrl, 'cesium.png'); var imagePath = getImagePath(objTexturedUrl, 'cesium.png');
expect(data.images[imagePath]).toBeDefined(); expect(data.images[imagePath]).toBeDefined();
expect(data.materials.MaterialTextured.diffuseColorMap).toEqual(imagePath); expect(data.materials.MaterialTextured.diffuseTexture).toEqual(imagePath);
}), done).toResolve(); }), done).toResolve();
}); });
it('does not load resources outside of the obj directory when secure is true', function(done) { it('does not load resources outside of the obj directory when secure is true', function(done) {
spyOn(console, 'log'); spyOn(console, 'log');
var options = {
secure : true var options = clone(defaultOptions);
}; options.secure = true;
expect(loadObj(objExternalResourcesUrl, options) expect(loadObj(objExternalResourcesUrl, options)
.then(function(data) { .then(function(data) {
var imagePath = getImagePath(objMissingTextureUrl, 'cesium.png'); var imagePath = getImagePath(objMissingTextureUrl, 'cesium.png');
@ -300,36 +305,36 @@ describe('obj', function() {
}); });
it('loads obj with texture', function(done) { it('loads obj with texture', function(done) {
expect(loadObj(objTexturedUrl) expect(loadObj(objTexturedUrl, defaultOptions)
.then(function(data) { .then(function(data) {
var imagePath = getImagePath(objTexturedUrl, 'cesium.png'); var imagePath = getImagePath(objTexturedUrl, 'cesium.png');
expect(data.images[imagePath]).toBeDefined(); expect(data.images[imagePath]).toBeDefined();
expect(data.materials.Material.diffuseColorMap).toEqual(imagePath); expect(data.materials.Material.diffuseTexture).toEqual(imagePath);
}), done).toResolve(); }), done).toResolve();
}); });
it('loads obj with missing texture', function(done) { it('loads obj with missing texture', function(done) {
spyOn(console, 'log'); spyOn(console, 'log');
expect(loadObj(objMissingTextureUrl) expect(loadObj(objMissingTextureUrl, defaultOptions)
.then(function(data) { .then(function(data) {
var imagePath = getImagePath(objMissingTextureUrl, 'cesium.png'); var imagePath = getImagePath(objMissingTextureUrl, 'cesium.png');
expect(data.images[imagePath]).toBeUndefined(); expect(data.images[imagePath]).toBeUndefined();
expect(data.materials.Material.diffuseColorMap).toEqual(imagePath); expect(data.materials.Material.diffuseTexture).toEqual(imagePath);
expect(console.log.calls.argsFor(0)[0].indexOf('Could not read image file') >= 0).toBe(true); expect(console.log.calls.argsFor(0)[0].indexOf('Could not read image file') >= 0).toBe(true);
}), done).toResolve(); }), done).toResolve();
}); });
it('loads obj with subdirectories', function(done) { it('loads obj with subdirectories', function(done) {
expect(loadObj(objSubdirectoriesUrl) expect(loadObj(objSubdirectoriesUrl, defaultOptions)
.then(function(data) { .then(function(data) {
var imagePath = getImagePath(objSubdirectoriesUrl, path.join('materials', 'images', 'cesium.png')); var imagePath = getImagePath(objSubdirectoriesUrl, path.join('materials', 'images', 'cesium.png'));
expect(data.images[imagePath]).toBeDefined(); expect(data.images[imagePath]).toBeDefined();
expect(data.materials.Material.diffuseColorMap).toEqual(imagePath); expect(data.materials.Material.diffuseTexture).toEqual(imagePath);
}), done).toResolve(); }), done).toResolve();
}); });
it('loads obj with complex material', function(done) { it('loads obj with complex material', function(done) {
expect(loadObj(objComplexMaterialUrl) expect(loadObj(objComplexMaterialUrl, defaultOptions)
.then(function(data) { .then(function(data) {
var images = data.images; var images = data.images;
expect(Object.keys(images).length).toBe(4); // Only ambient, diffuse, emission, and specular maps are supported by the converter expect(Object.keys(images).length).toBe(4); // Only ambient, diffuse, emission, and specular maps are supported by the converter
@ -337,10 +342,10 @@ describe('obj', function() {
}); });
it('does not process file with invalid contents', function(done) { it('does not process file with invalid contents', function(done) {
expect(loadObj(objInvalidContentsUrl), done).toRejectWith(RuntimeError); expect(loadObj(objInvalidContentsUrl, defaultOptions), done).toRejectWith(RuntimeError);
}); });
it('throw when reading invalid file', function(done) { it('throw when reading invalid file', function(done) {
expect(loadObj(objInvalidUrl), done).toRejectWith(Error); expect(loadObj(objInvalidUrl, defaultOptions), done).toRejectWith(Error);
}); });
}); });