mirror of
https://github.com/CesiumGS/obj2gltf.git
synced 2024-11-27 10:30:16 -05:00
Many updates
This commit is contained in:
parent
28d081e0ae
commit
cc8fee19c4
31
README.md
31
README.md
@ -11,45 +11,40 @@ npm install --save obj2gltf
|
||||
Using obj2gltf as a library:
|
||||
```javascript
|
||||
var obj2gltf = require('obj2gltf');
|
||||
var convert = obj2gltf.convert;
|
||||
var options = {
|
||||
separateTextures : true // Don't embed textures in the converted glTF
|
||||
}
|
||||
convert('model.obj', 'model.gltf', options)
|
||||
obj2gltf('model.obj', 'model.gltf', options)
|
||||
.then(function() {
|
||||
console.log('Converted model');
|
||||
});
|
||||
```
|
||||
Using obj2gltf as a command-line tool:
|
||||
|
||||
`node bin/obj2gltf.js model.obj`
|
||||
|
||||
`node bin/obj2gltf.js model.obj model.gltf`
|
||||
`node bin/obj2gltf.js -i model.obj`
|
||||
|
||||
`node bin/obj2gltf.js -i model.obj -o model.gltf`
|
||||
|
||||
`node bin/obj2gltf.js -i model.obj -o model.gltf -s`
|
||||
|
||||
## Usage
|
||||
|
||||
###Command line flags:
|
||||
|
||||
|Flag|Description|Required|
|
||||
|----|-----------|--------|
|
||||
|`-h`|Display help.|No|
|
||||
|`-i`|Path to the obj file.| :white_check_mark: Yes|
|
||||
|`-o`|Path of the converted glTF file.|No|
|
||||
|`-b`|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`|
|
||||
|`-t`|Write out separate textures only.|No, default `false`|
|
||||
|`-c`|Quantize positions, compress texture coordinates, and oct-encode normals.|No, default `false`|
|
||||
|`-z`|Use the optimization stages in the glTF pipeline.|No, default `false`|
|
||||
|`-n`|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`|
|
||||
|`-h`, `--help`|Display help.|No|
|
||||
|`-i`, `--input`|Path to the obj file.| :white_check_mark: Yes|
|
||||
|`-o`, `--output`|Path of the converted glTF file.|No|
|
||||
|`-b`, `--binary`|Save as binary glTF.|No, default `false`|
|
||||
|`-s`, `--separate`|Writes out separate geometry data files, shader files, and textures instead of embedding them in the glTF file.|No, default `false`|
|
||||
|`-t`, `--separateTextures`|Write out separate textures only.|No, default `false`|
|
||||
|`-c`, `--compress`|Quantize positions, compress texture coordinates, and oct-encode normals.|No, default `false`|
|
||||
|`-z`, `--optimize`|Use the optimization stages in the glTF pipeline.|No, default `false`|
|
||||
|`-n`, `--generateNormals`|Generate normals if they are missing.|No, default `false`|
|
||||
|`--optimizeForCesium`|Optimize the glTF for Cesium by using the sun as a default light source.|No, default `false`|
|
||||
|`--ao`|Apply ambient occlusion to the converted model.|No, default `false`|
|
||||
|`--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`|
|
||||
|`--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`|
|
||||
|
||||
## Build Instructions
|
||||
|
@ -5,11 +5,11 @@ var path = require('path');
|
||||
var yargs = require('yargs');
|
||||
var convert = require('../lib/convert');
|
||||
|
||||
var defaultValue = Cesium.defaultValue;
|
||||
var defined = Cesium.defined;
|
||||
|
||||
var defaults = convert.defaults;
|
||||
|
||||
var args = process.argv;
|
||||
args = args.slice(2, args.length);
|
||||
|
||||
var argv = yargs
|
||||
.usage('Usage: node $0 -i inputPath -o outputPath')
|
||||
@ -21,7 +21,8 @@ var argv = yargs
|
||||
alias: 'i',
|
||||
describe: 'Path to the obj file.',
|
||||
type: 'string',
|
||||
normalize: true
|
||||
normalize: true,
|
||||
demandOption: true
|
||||
},
|
||||
output : {
|
||||
alias: 'o',
|
||||
@ -33,77 +34,72 @@ var argv = yargs
|
||||
alias: 'b',
|
||||
describe: 'Save as binary glTF.',
|
||||
type: 'boolean',
|
||||
default: false
|
||||
default: defaults.binary
|
||||
},
|
||||
separate : {
|
||||
alias: 's',
|
||||
describe: 'Write separate geometry data files, shader files, and textures instead of embedding them in the glTF.',
|
||||
type: 'boolean',
|
||||
default: false
|
||||
default: defaults.separate
|
||||
},
|
||||
separateTextures : {
|
||||
alias: 't',
|
||||
describe: 'Write out separate textures only.',
|
||||
type: 'boolean',
|
||||
default: false
|
||||
default: defaults.separateTextures
|
||||
},
|
||||
compress : {
|
||||
alias: 'c',
|
||||
describe: 'Quantize positions, compress texture coordinates, and oct-encode normals.',
|
||||
type: 'boolean',
|
||||
default: false
|
||||
default: defaults.compress
|
||||
},
|
||||
optimize : {
|
||||
alias: 'z',
|
||||
describe: 'Use the optimization stages in the glTF pipeline.',
|
||||
describe: 'Optimize the glTF for size and runtime performance.',
|
||||
type: 'boolean',
|
||||
default: false
|
||||
default: defaults.optimize
|
||||
},
|
||||
cesium : {
|
||||
optimizeForCesium : {
|
||||
describe: 'Optimize the glTF for Cesium by using the sun as a default light source.',
|
||||
type: 'boolean',
|
||||
default: false
|
||||
default: defaults.optimizeForCesium
|
||||
},
|
||||
generateNormals : {
|
||||
alias: 'n',
|
||||
describe: 'Generate normals if they are missing.',
|
||||
type: 'boolean',
|
||||
default: false
|
||||
default: defaults.generateNormals
|
||||
},
|
||||
ao : {
|
||||
describe: 'Apply ambient occlusion to the converted model.',
|
||||
type: 'boolean',
|
||||
default: false
|
||||
default: defaults.ao
|
||||
},
|
||||
kmc : {
|
||||
describe: 'Output glTF with the KHR_materials_common extension.',
|
||||
type: 'boolean',
|
||||
default: false
|
||||
default: defaults.kmc
|
||||
},
|
||||
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.',
|
||||
type: 'boolean',
|
||||
default: false
|
||||
default: defaults.bypassPipeline
|
||||
},
|
||||
hasTransparency : {
|
||||
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.',
|
||||
checkTransparency : {
|
||||
describe: 'Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures are considered to be opaque.',
|
||||
type: 'boolean',
|
||||
default: false
|
||||
default: defaults.checkTransparency
|
||||
},
|
||||
secure : {
|
||||
describe: 'Prevent the converter from reading image or mtl files outside of the input obj directory.',
|
||||
type: 'boolean',
|
||||
default: false
|
||||
default: defaults.secure
|
||||
}
|
||||
}).parse(args);
|
||||
|
||||
var objPath = defaultValue(argv.i, argv._[0]);
|
||||
var gltfPath = defaultValue(argv.o, argv._[1]);
|
||||
|
||||
if (!defined(objPath)) {
|
||||
yargs.showHelp();
|
||||
return;
|
||||
}
|
||||
var objPath = argv.i;
|
||||
var gltfPath = argv.o;
|
||||
|
||||
if (!defined(gltfPath)) {
|
||||
var extension = argv.b ? '.glb' : '.gltf';
|
||||
@ -112,16 +108,17 @@ if (!defined(gltfPath)) {
|
||||
}
|
||||
|
||||
var options = {
|
||||
binary : argv.b,
|
||||
separate : argv.s,
|
||||
separateTextures : argv.t,
|
||||
compress : argv.c,
|
||||
optimize : argv.z,
|
||||
generateNormals : argv.n,
|
||||
binary : argv.binary,
|
||||
separate : argv.separate,
|
||||
separateTextures : argv.separateTextures,
|
||||
compress : argv.compress,
|
||||
optimize : argv.optimize,
|
||||
optimizeForCesium : argv.optimizeForCesium,
|
||||
generateNormals : argv.generateNormals,
|
||||
ao : argv.ao,
|
||||
optimizeForCesium : argv.cesium,
|
||||
kmc : argv.kmc,
|
||||
bypassPipeline : argv.bypassPipeline,
|
||||
hasTransparency : argv.hasTransparency,
|
||||
checkTransparency : argv.checkTransparency,
|
||||
secure : argv.secure
|
||||
};
|
||||
|
||||
|
4
index.js
4
index.js
@ -1,3 +1 @@
|
||||
module.exports = {
|
||||
convert : require('./lib/convert')
|
||||
};
|
||||
module.exports = require('./lib/convert');
|
||||
|
19
lib/Material.js
Normal file
19
lib/Material.js
Normal 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
|
||||
}
|
54
lib/clone.js
54
lib/clone.js
@ -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;
|
||||
}
|
163
lib/convert.js
163
lib/convert.js
@ -16,16 +16,6 @@ var DeveloperError = Cesium.DeveloperError;
|
||||
|
||||
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.
|
||||
*
|
||||
@ -36,35 +26,42 @@ 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.separateTextures=false] Write out separate textures only.
|
||||
* @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.generateNormals=false] Generate normals if they are missing.
|
||||
* @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.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.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 {Logger} [options.logger] A callback function for handling logged messages. Defaults to console.log.
|
||||
*/
|
||||
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 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, false);
|
||||
var logger = defaultValue(options.logger, defaultLogger);
|
||||
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);
|
||||
|
||||
options.separate = separate;
|
||||
options.separateTextures = separateTextures;
|
||||
options.checkTransparency = checkTransparency;
|
||||
options.secure = secure;
|
||||
options.logger = logger;
|
||||
options.hasTransparency = defaultValue(options.hasTransparency, false);
|
||||
options.secure = defaultValue(options.secure, false);
|
||||
|
||||
|
||||
if (!defined(objPath)) {
|
||||
throw new DeveloperError('objPath is required');
|
||||
@ -74,25 +71,17 @@ function convert(objPath, gltfPath, options) {
|
||||
throw new DeveloperError('gltfPath is required');
|
||||
}
|
||||
|
||||
var objExtension = path.extname(objPath).toLowerCase();
|
||||
if (objExtension !== '.obj') {
|
||||
throw new DeveloperError('Invalid obj path "' + objPath + '"');
|
||||
}
|
||||
|
||||
var extension = path.extname(gltfPath).toLowerCase();
|
||||
if (extension !== '.gltf' && extension !== '.glb') {
|
||||
throw new DeveloperError('Invalid gltf path "' + gltfPath + '"');
|
||||
}
|
||||
|
||||
var basePath = path.dirname(gltfPath);
|
||||
var modelName = path.basename(gltfPath, path.extname(gltfPath));
|
||||
if (extension === '.glb') {
|
||||
binary = true;
|
||||
if (bypassPipeline) {
|
||||
logger('--bypassPipeline does not convert to binary glTF, saving as .gltf');
|
||||
extension = '.gltf';
|
||||
}
|
||||
|
||||
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;
|
||||
@ -120,7 +109,7 @@ function convert(objPath, gltfPath, options) {
|
||||
return createGltf(objData);
|
||||
})
|
||||
.then(function(gltf) {
|
||||
return writeUris(gltf, gltfPath, separate, separateTextures, logger);
|
||||
return writeUris(gltf, gltfPath, options);
|
||||
})
|
||||
.then(function(gltf) {
|
||||
if (bypassPipeline) {
|
||||
@ -128,15 +117,105 @@ function convert(objPath, gltfPath, options) {
|
||||
} 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
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
convert._outputJson = fsExtraOutputJson;
|
||||
|
||||
/**
|
||||
* A callback function that logs messages.
|
||||
* @callback Logger
|
||||
*
|
||||
* @param {String} message The message to log.
|
||||
*/
|
||||
|
||||
|
19
lib/gltf.js
19
lib/gltf.js
@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
var Cesium = require('cesium');
|
||||
var path = require('path');
|
||||
var Material = require('./Material');
|
||||
|
||||
var defined = Cesium.defined;
|
||||
var defaultValue = Cesium.defaultValue;
|
||||
@ -67,10 +68,10 @@ function createGltf(objData) {
|
||||
}
|
||||
|
||||
function createMaterial(material, hasNormals) {
|
||||
var ambient = defaultValue(defaultValue(getTextureId(material.ambientColorMap), material.ambientColor), [0, 0, 0, 1]);
|
||||
var diffuse = defaultValue(defaultValue(getTextureId(material.diffuseColorMap), material.diffuseColor), [0.5, 0.5, 0.5, 1]);
|
||||
var emission = defaultValue(defaultValue(getTextureId(material.emissionColorMap), material.emissionColor), [0, 0, 0, 1]);
|
||||
var specular = defaultValue(defaultValue(getTextureId(material.specularColorMap), material.specularColor), [0, 0, 0, 1]);
|
||||
var ambient = defaultValue(defaultValue(getTextureId(material.ambientTexture), material.ambientColor));
|
||||
var diffuse = defaultValue(defaultValue(getTextureId(material.diffuseTexture), material.diffuseColor));
|
||||
var emission = defaultValue(defaultValue(getTextureId(material.emissionTexture), material.emissionColor));
|
||||
var specular = defaultValue(defaultValue(getTextureId(material.specularTexture), material.specularColor));
|
||||
var alpha = defaultValue(defaultValue(material.alpha), 1.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);
|
||||
@ -79,7 +80,7 @@ function createGltf(objData) {
|
||||
var transparency = 1.0;
|
||||
if (typeof diffuse === 'string') {
|
||||
transparency = alpha;
|
||||
transparent = images[material.diffuseColorMap].transparent || (transparency < 1.0);
|
||||
transparent = images[material.diffuseTexture].transparent || (transparency < 1.0);
|
||||
} else {
|
||||
diffuse[3] = alpha;
|
||||
transparent = diffuse[3] < 1.0;
|
||||
@ -93,11 +94,6 @@ function createGltf(objData) {
|
||||
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';
|
||||
return {
|
||||
extensions : {
|
||||
@ -287,7 +283,8 @@ function createGltf(objData) {
|
||||
materialId = 'default';
|
||||
}
|
||||
|
||||
var material = defaultValue(materials[materialId], {});
|
||||
var material = materials[materialId];
|
||||
material = defined(material) ? material : new Material();
|
||||
var gltfMaterial = gltf.materials[materialId];
|
||||
if (defined(gltfMaterial)) {
|
||||
// Check if this material has already been added but with incompatible shading
|
||||
|
27
lib/image.js
27
lib/image.js
@ -7,7 +7,7 @@ var Promise = require('bluebird');
|
||||
|
||||
var fsExtraReadFile = Promise.promisify(fsExtra.readFile);
|
||||
|
||||
var defaultValue = Cesium.defaultValue;
|
||||
var defined = Cesium.defined;
|
||||
var WebGLConstants = Cesium.WebGLConstants;
|
||||
|
||||
module.exports = loadImage;
|
||||
@ -16,19 +16,16 @@ module.exports = loadImage;
|
||||
* Load an image file and get information about it.
|
||||
*
|
||||
* @param {String} imagePath Path to the image file.
|
||||
* @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 {Object} options An object with the following properties:
|
||||
* @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.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function loadImage(imagePath, options) {
|
||||
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
|
||||
var hasTransparency = defaultValue(options.hasTransparency, false);
|
||||
|
||||
return fsExtraReadFile(imagePath)
|
||||
.then(function(data) {
|
||||
var extension = path.extname(imagePath);
|
||||
var extension = path.extname(imagePath).toLowerCase();
|
||||
|
||||
var info = {
|
||||
transparent : false,
|
||||
@ -38,14 +35,21 @@ function loadImage(imagePath, options) {
|
||||
};
|
||||
|
||||
if (extension === '.png') {
|
||||
return getPngInfo(data, info, options);
|
||||
}
|
||||
|
||||
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) {
|
||||
info.transparent = true;
|
||||
if (hasTransparency) {
|
||||
if (options.checkTransparency) {
|
||||
return isTransparent(data)
|
||||
.then(function(transparent) {
|
||||
info.transparent = transparent;
|
||||
@ -53,16 +57,13 @@ function loadImage(imagePath, options) {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
});
|
||||
}
|
||||
|
||||
function isTransparent(data) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
new PNG().parse(data, function(error, data) {
|
||||
if (error) {
|
||||
if (defined(error)) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
41
lib/mtl.js
41
lib/mtl.js
@ -1,30 +1,15 @@
|
||||
'use strict';
|
||||
var path = require('path');
|
||||
var Material = require('./Material');
|
||||
var readLines = require('./readLines');
|
||||
|
||||
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.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
@ -32,6 +17,7 @@ function loadMtl(mtlPath) {
|
||||
var material;
|
||||
var values;
|
||||
var value;
|
||||
var mtlDirectory = path.dirname(mtlPath);
|
||||
var materials = {};
|
||||
|
||||
function parseLine(line) {
|
||||
@ -82,19 +68,19 @@ function loadMtl(mtlPath) {
|
||||
value = line.substring(3).trim();
|
||||
material.alpha = parseFloat(value);
|
||||
} 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)) {
|
||||
material.emissionColorMap = getAbsolutePath(line.substring(7).trim(), mtlPath);
|
||||
material.emissionTexture = path.resolve(mtlDirectory, line.substring(7).trim());
|
||||
} 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)) {
|
||||
material.specularColorMap = getAbsolutePath(line.substring(7).trim(), mtlPath);
|
||||
material.specularTexture = path.resolve(mtlDirectory, line.substring(7).trim());
|
||||
} 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)) {
|
||||
material.normalMap = getAbsolutePath(line.substring(9).trim(), mtlPath);
|
||||
material.normalMap = path.resolve(mtlDirectory, line.substring(9).trim());
|
||||
} 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;
|
||||
});
|
||||
}
|
||||
|
||||
function getAbsolutePath(imagePath, mtlPath) {
|
||||
if (!path.isAbsolute(imagePath)) {
|
||||
imagePath = path.join(path.dirname(mtlPath), imagePath);
|
||||
}
|
||||
return imagePath;
|
||||
}
|
||||
|
107
lib/obj.js
107
lib/obj.js
@ -8,7 +8,6 @@ var loadImage = require('./image');
|
||||
var loadMtl = require('./mtl');
|
||||
var readLines = require('./readLines');
|
||||
|
||||
var combine = Cesium.combine;
|
||||
var ComponentDatatype = Cesium.ComponentDatatype;
|
||||
var defaultValue = Cesium.defaultValue;
|
||||
var defined = Cesium.defined;
|
||||
@ -51,25 +50,16 @@ var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(
|
||||
* Parse an obj file.
|
||||
*
|
||||
* @param {String} objPath Path to the obj file.
|
||||
* @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.secure=false] 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 {Object} options An object with the following properties:
|
||||
* @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 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.
|
||||
* @returns {Promise} A promise resolving to the obj data.
|
||||
* @exception {RuntimeError} The file does not have any geometry information in it.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
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
|
||||
var positions = new ArrayStorage(ComponentDatatype.FLOAT);
|
||||
var normals = new ArrayStorage(ComponentDatatype.FLOAT);
|
||||
@ -293,7 +283,7 @@ function loadObj(objPath, options) {
|
||||
function finishLoading(nodes, mtlPaths, objPath, options) {
|
||||
nodes = cleanNodes(nodes);
|
||||
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)
|
||||
.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) {
|
||||
return (path.relative(path.dirname(objPath), filePath).indexOf('..') === 0);
|
||||
}
|
||||
|
||||
function loadMaterials(mtlPaths, objPath, options) {
|
||||
var secure = options.secure;
|
||||
var logger = options.logger;
|
||||
var objDirectory = path.dirname(objPath);
|
||||
var materials = {};
|
||||
return Promise.map(mtlPaths, function(mtlPath) {
|
||||
mtlPath = getAbsolutePath(mtlPath, objPath);
|
||||
if (options.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.');
|
||||
mtlPath = path.resolve(objDirectory, mtlPath);
|
||||
if (secure && outsideDirectory(mtlPath, objPath)) {
|
||||
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 loadMtl(mtlPath)
|
||||
.then(function(materialsInMtl) {
|
||||
materials = combine(materials, materialsInMtl);
|
||||
materials = Object.assign(materials, materialsInMtl);
|
||||
})
|
||||
.catch(function() {
|
||||
options.logger('Could not read mtl file at ' + mtlPath + '. Using default material instead.');
|
||||
});
|
||||
}).then(function() {
|
||||
return materials;
|
||||
logger('Could not read mtl file at ' + mtlPath + '. Using default material instead.');
|
||||
});
|
||||
}, {concurrency : 10})
|
||||
.thenReturn(materials);
|
||||
}
|
||||
|
||||
function loadImages(imagePaths, objPath, options) {
|
||||
var secure = options.secure;
|
||||
var logger = options.logger;
|
||||
var images = {};
|
||||
return Promise.map(imagePaths, function(imagePath) {
|
||||
if (options.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.');
|
||||
if (secure && outsideDirectory(imagePath, objPath)) {
|
||||
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 loadImage(imagePath, options)
|
||||
.then(function(image) {
|
||||
if (defined(image)) {
|
||||
images[imagePath] = image;
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
options.logger('Could not read image file at ' + imagePath + '. Material will ignore this image.');
|
||||
return undefined;
|
||||
});
|
||||
}).then(function() {
|
||||
return images;
|
||||
logger('Could not read image file at ' + imagePath + '. Material will ignore this image.');
|
||||
});
|
||||
}, {concurrency : 10})
|
||||
.thenReturn(images);
|
||||
}
|
||||
|
||||
function getImagePaths(materials) {
|
||||
@ -367,46 +350,32 @@ function getImagePaths(materials) {
|
||||
for (var name in materials) {
|
||||
if (materials.hasOwnProperty(name)) {
|
||||
var material = materials[name];
|
||||
if (defined(material.ambientColorMap)) {
|
||||
imagePaths[material.ambientColorMap] = true;
|
||||
if (defined(material.ambientTexture)) {
|
||||
imagePaths[material.ambientTexture] = true;
|
||||
}
|
||||
if (defined(material.diffuseColorMap)) {
|
||||
imagePaths[material.diffuseColorMap] = true;
|
||||
if (defined(material.diffuseTexture)) {
|
||||
imagePaths[material.diffuseTexture] = true;
|
||||
}
|
||||
if (defined(material.emissionColorMap)) {
|
||||
imagePaths[material.emissionColorMap] = true;
|
||||
if (defined(material.emissionTexture)) {
|
||||
imagePaths[material.emissionTexture] = true;
|
||||
}
|
||||
if (defined(material.specularColorMap)) {
|
||||
imagePaths[material.specularColorMap] = true;
|
||||
if (defined(material.specularTexture)) {
|
||||
imagePaths[material.specularTexture] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
var final = [];
|
||||
var meshesLength = meshes.length;
|
||||
for (var i = 0; i < meshesLength; ++i) {
|
||||
var mesh = meshes[i];
|
||||
mesh.primitives = removeEmptyPrimitives(mesh.primitives);
|
||||
if ((mesh.primitives.length > 0) && (mesh.positions.length > 0)) {
|
||||
final.push(mesh);
|
||||
}
|
||||
}
|
||||
return final;
|
||||
return meshes.filter(function(mesh) {
|
||||
// Remove empty primitives
|
||||
mesh.primitives = mesh.primitives.filter(function(primitive) {
|
||||
return primitive.indices.length > 0;
|
||||
});
|
||||
// Valid meshes must have at least one primitive and contain positions
|
||||
return (mesh.primitives.length > 0) && (mesh.positions.length > 0);
|
||||
});
|
||||
}
|
||||
|
||||
function meshesHaveNames(meshes) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
'use strict';
|
||||
var Cesium = require('cesium');
|
||||
var fsExtra = require('fs-extra');
|
||||
var mime = require('mime');
|
||||
var path = require('path');
|
||||
@ -6,6 +7,8 @@ var Promise = require('bluebird');
|
||||
|
||||
var fsExtraOutputFile = Promise.promisify(fsExtra.outputFile);
|
||||
|
||||
var RuntimeError = Cesium.RuntimeError;
|
||||
|
||||
module.exports = writeUris;
|
||||
|
||||
/**
|
||||
@ -13,14 +16,17 @@ module.exports = writeUris;
|
||||
*
|
||||
* @param {Object} gltf The glTF asset.
|
||||
* @param {String} gltfPath Path where the glTF will be saved.
|
||||
* @param {Boolean} separateBuffers Writes out separate buffers.
|
||||
* @param {Boolean} separateTextures Writes out separate textures.
|
||||
* @param {Logger} logger A callback function for handling logged messages. Defaults to console.log.
|
||||
* @param {Object} options An object with the following properties:
|
||||
* @param {Boolean} options.separate Writes out separate buffers.
|
||||
* @param {Boolean} options.separateTextures Write out separate textures only.
|
||||
* @returns {Promise} A promise that resolves to the glTF asset.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function writeUris(gltf, gltfPath, separateBuffers, separateTextures, logger) {
|
||||
function writeUris(gltf, gltfPath, options) {
|
||||
var separate = options.separate;
|
||||
var separateTextures = options.separateTextures;
|
||||
|
||||
var promises = [];
|
||||
|
||||
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
|
||||
var exceedsMaximum = (texturesByteLength + bufferByteLength > 201326580);
|
||||
|
||||
if (exceedsMaximum) {
|
||||
logger('Buffers and textures are too large to encode in the glTF, saving as separate resources.');
|
||||
if (exceedsMaximum && !separate) {
|
||||
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));
|
||||
} else {
|
||||
writeEmbeddedBuffer(gltf);
|
||||
}
|
||||
|
||||
if (separateTextures || exceedsMaximum) {
|
||||
if (separateTextures) {
|
||||
promises.push(writeSeparateTextures(gltf, gltfPath));
|
||||
} else {
|
||||
writeEmbeddedTextures(gltf);
|
||||
@ -84,19 +90,15 @@ function writeSeparateBuffer(gltf, gltfPath) {
|
||||
}
|
||||
|
||||
function writeSeparateTextures(gltf, gltfPath) {
|
||||
var promises = [];
|
||||
var images = gltf.images;
|
||||
for (var id in images) {
|
||||
if (images.hasOwnProperty(id)) {
|
||||
return Promise.map(Object.keys(images), function(id) {
|
||||
var image = images[id];
|
||||
var extras = image.extras._obj2gltf;
|
||||
var imageUri = image.name + extras.extension;
|
||||
image.uri = imageUri;
|
||||
var imagePath = path.join(path.dirname(gltfPath), imageUri);
|
||||
promises.push(writeUris._outputFile(imagePath, extras.source));
|
||||
}
|
||||
}
|
||||
return Promise.all(promises);
|
||||
return writeUris._outputFile(imagePath, extras.source);
|
||||
}, {concurrency : 10});
|
||||
}
|
||||
|
||||
function writeEmbeddedBuffer(gltf) {
|
||||
@ -106,7 +108,6 @@ function writeEmbeddedBuffer(gltf) {
|
||||
}
|
||||
|
||||
function writeEmbeddedTextures(gltf) {
|
||||
var promises = [];
|
||||
var images = gltf.images;
|
||||
for (var id in images) {
|
||||
if (images.hasOwnProperty(id)) {
|
||||
@ -115,7 +116,6 @@ function writeEmbeddedTextures(gltf) {
|
||||
image.uri = 'data:' + mime.lookup(extras.extension) + ';base64,' + extras.source.toString('base64');
|
||||
}
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,20 +1,13 @@
|
||||
'use strict';
|
||||
var Cesium = require('cesium');
|
||||
var GltfPipeline = require('gltf-pipeline').Pipeline;
|
||||
var path = require('path');
|
||||
var convert = require('../../lib/convert');
|
||||
var writeUris = require('../../lib/writeUris');
|
||||
|
||||
var DeveloperError = Cesium.DeveloperError;
|
||||
|
||||
var objPath = 'specs/data/box-textured/box-textured.obj';
|
||||
var gltfPath = 'specs/data/box-textured/box-textured.gltf';
|
||||
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 gltfPathNonExistent = 'specs/data/non-existent.gltf';
|
||||
|
||||
var objExternalResourcesPath = 'specs/data/box-external-resources/box-external-resources.obj';
|
||||
|
||||
describe('convert', function() {
|
||||
@ -57,8 +50,8 @@ describe('convert', function() {
|
||||
});
|
||||
|
||||
it('sets options', function(done) {
|
||||
var spy1 = spyOn(GltfPipeline, 'processJSONToDisk');
|
||||
var spy2 = spyOn(writeUris, '_outputFile');
|
||||
var spy = spyOn(GltfPipeline, 'processJSONToDisk');
|
||||
spyOn(writeUris, '_outputFile');
|
||||
var textureCompressionOptions = {
|
||||
format : 'dxt1',
|
||||
quality : 10
|
||||
@ -69,16 +62,19 @@ describe('convert', function() {
|
||||
separateTextures : true,
|
||||
compress : true,
|
||||
optimize : true,
|
||||
optimizeForCesium : true,
|
||||
generateNormals : true,
|
||||
ao : true,
|
||||
kmc : true,
|
||||
optimizeForCesium : true,
|
||||
textureCompressionOptions : textureCompressionOptions
|
||||
textureCompressionOptions : textureCompressionOptions,
|
||||
checkTransparency : true,
|
||||
secure : true,
|
||||
logger : convert.defaults.logger
|
||||
};
|
||||
|
||||
expect(convert(objPath, gltfPath, options)
|
||||
.then(function() {
|
||||
var args = spy1.calls.first().args;
|
||||
var args = spy.calls.first().args;
|
||||
var options = args[2];
|
||||
expect(options).toEqual({
|
||||
createDirectory : false,
|
||||
@ -96,7 +92,7 @@ describe('convert', function() {
|
||||
textureCompressionOptions : textureCompressionOptions,
|
||||
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();
|
||||
});
|
||||
|
||||
@ -111,50 +107,31 @@ describe('convert', function() {
|
||||
});
|
||||
|
||||
it('bypassPipeline flag bypasses gltf-pipeline', function(done) {
|
||||
var spy1 = spyOn(convert, '_outputJson');
|
||||
var spy2 = spyOn(GltfPipeline, 'processJSONToDisk');
|
||||
spyOn(convert, '_outputJson');
|
||||
spyOn(GltfPipeline, 'processJSONToDisk');
|
||||
var options = {
|
||||
bypassPipeline : true
|
||||
};
|
||||
expect(convert(objPath, gltfPath, options)
|
||||
.then(function() {
|
||||
expect(spy1.calls.count()).toBe(1);
|
||||
expect(spy2.calls.count()).toBe(0);
|
||||
expect(convert._outputJson).toHaveBeenCalled();
|
||||
expect(GltfPipeline.processJSONToDisk).not.toHaveBeenCalled();
|
||||
}), 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) {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
@ -3,12 +3,14 @@ var Cesium = require('cesium');
|
||||
var fsExtra = require('fs-extra');
|
||||
var path = require('path');
|
||||
var Promise = require('bluebird');
|
||||
var clone = require('../../lib/clone.js');
|
||||
var createGltf = require('../../lib/gltf.js');
|
||||
var loadImage = require('../../lib/image.js');
|
||||
var loadObj = require('../../lib/obj.js');
|
||||
var writeUris = require('../../lib/writeUris.js');
|
||||
var convert = require('../../lib/convert');
|
||||
var createGltf = require('../../lib/gltf');
|
||||
var loadImage = require('../../lib/image');
|
||||
var loadObj = require('../../lib/obj');
|
||||
var Material = require('../../lib/Material');
|
||||
var writeUris = require('../../lib/writeUris');
|
||||
|
||||
var clone = Cesium.clone;
|
||||
var WebGLConstants = Cesium.WebGLConstants;
|
||||
|
||||
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 transparentDiffuseTextureUrl = 'specs/data/box-complex-material/diffuse.png';
|
||||
|
||||
var defaultOptions = convert.defaults;
|
||||
var checkTransparencyOptions = clone(defaultOptions);
|
||||
checkTransparencyOptions.checkTransparency = true;
|
||||
|
||||
describe('gltf', function() {
|
||||
var boxObjData;
|
||||
var duplicateBoxObjData;
|
||||
var groupObjData;
|
||||
var boxGltf;
|
||||
var groupGltf;
|
||||
var diffuseTexture;
|
||||
var transparentDiffuseTexture;
|
||||
|
||||
beforeAll(function(done) {
|
||||
beforeEach(function(done) {
|
||||
return Promise.all([
|
||||
loadObj(boxObjUrl)
|
||||
loadObj(boxObjUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
boxObjData = data;
|
||||
}),
|
||||
loadObj(groupObjUrl)
|
||||
loadObj(boxObjUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
duplicateBoxObjData = data;
|
||||
}),
|
||||
loadObj(groupObjUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
groupObjData = data;
|
||||
}),
|
||||
@ -46,11 +57,11 @@ describe('gltf', function() {
|
||||
.then(function(gltf) {
|
||||
groupGltf = gltf;
|
||||
}),
|
||||
loadImage(diffuseTextureUrl)
|
||||
loadImage(diffuseTextureUrl, defaultOptions)
|
||||
.then(function(image) {
|
||||
diffuseTexture = image;
|
||||
}),
|
||||
loadImage(transparentDiffuseTextureUrl)
|
||||
loadImage(transparentDiffuseTextureUrl, checkTransparencyOptions)
|
||||
.then(function(image) {
|
||||
transparentDiffuseTexture = image;
|
||||
})
|
||||
@ -58,19 +69,17 @@ describe('gltf', function() {
|
||||
});
|
||||
|
||||
it('simple gltf', function(done) {
|
||||
var objData = clone(boxObjData, true);
|
||||
var gltf = createGltf(objData);
|
||||
expect(writeUris(gltf, boxGltfUrl, false, false)
|
||||
var gltf = createGltf(boxObjData);
|
||||
expect(writeUris(gltf, boxGltfUrl, defaultOptions)
|
||||
.then(function() {
|
||||
expect(gltf).toEqual(boxGltf);
|
||||
}), done).toResolve();
|
||||
});
|
||||
|
||||
it('multiple nodes, meshes, and primitives', function(done) {
|
||||
var objData = clone(groupObjData, true);
|
||||
var gltf = createGltf(objData);
|
||||
var gltf = createGltf(groupObjData);
|
||||
|
||||
expect(writeUris(gltf, groupGltfUrl, false, false)
|
||||
expect(writeUris(gltf, groupGltfUrl, defaultOptions)
|
||||
.then(function() {
|
||||
expect(gltf).toEqual(groupGltf);
|
||||
expect(Object.keys(gltf.materials).length).toBe(3);
|
||||
@ -88,10 +97,9 @@ describe('gltf', function() {
|
||||
});
|
||||
|
||||
it('sets default material values', function() {
|
||||
var objData = clone(boxObjData, true);
|
||||
objData.materials.Material = {};
|
||||
boxObjData.materials.Material = new Material();
|
||||
|
||||
var gltf = createGltf(objData);
|
||||
var gltf = createGltf(boxObjData);
|
||||
var material = gltf.materials.Material;
|
||||
var kmc = material.extensions.KHR_materials_common;
|
||||
var values = kmc.values;
|
||||
@ -105,13 +113,12 @@ describe('gltf', function() {
|
||||
});
|
||||
|
||||
it('sets material for diffuse texture', function() {
|
||||
var objData = clone(boxObjData, true);
|
||||
objData.materials.Material = {
|
||||
diffuseColorMap : diffuseTextureUrl
|
||||
};
|
||||
objData.images[diffuseTextureUrl] = diffuseTexture;
|
||||
var material = new Material();
|
||||
material.diffuseTexture = diffuseTextureUrl;
|
||||
boxObjData.materials.Material = material;
|
||||
boxObjData.images[diffuseTextureUrl] = diffuseTexture;
|
||||
|
||||
var gltf = createGltf(objData);
|
||||
var gltf = createGltf(boxObjData);
|
||||
var kmc = gltf.materials.Material.extensions.KHR_materials_common;
|
||||
var texture = gltf.textures.texture_cesium;
|
||||
var image = gltf.images.cesium;
|
||||
@ -145,12 +152,11 @@ describe('gltf', function() {
|
||||
});
|
||||
|
||||
it('sets material for alpha less than 1', function() {
|
||||
var objData = clone(boxObjData, true);
|
||||
objData.materials.Material = {
|
||||
alpha : 0.4
|
||||
};
|
||||
var material = new Material();
|
||||
material.alpha = 0.4;
|
||||
boxObjData.materials.Material = material;
|
||||
|
||||
var gltf = createGltf(objData);
|
||||
var gltf = createGltf(boxObjData);
|
||||
var kmc = gltf.materials.Material.extensions.KHR_materials_common;
|
||||
|
||||
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() {
|
||||
var objData = clone(boxObjData, true);
|
||||
objData.materials.Material = {
|
||||
diffuseColorMap : diffuseTextureUrl,
|
||||
alpha : 0.4
|
||||
};
|
||||
objData.images[diffuseTextureUrl] = diffuseTexture;
|
||||
var material = new Material();
|
||||
material.diffuseTexture = diffuseTextureUrl;
|
||||
material.alpha = 0.4;
|
||||
boxObjData.materials.Material = material;
|
||||
|
||||
var gltf = createGltf(objData);
|
||||
boxObjData.images[diffuseTextureUrl] = diffuseTexture;
|
||||
|
||||
var gltf = createGltf(boxObjData);
|
||||
var kmc = gltf.materials.Material.extensions.KHR_materials_common;
|
||||
|
||||
expect(kmc.values.diffuse).toEqual('texture_cesium');
|
||||
@ -177,13 +183,13 @@ describe('gltf', function() {
|
||||
});
|
||||
|
||||
it('sets material for transparent diffuse texture', function() {
|
||||
var objData = clone(boxObjData, true);
|
||||
objData.materials.Material = {
|
||||
diffuseColorMap : transparentDiffuseTextureUrl
|
||||
};
|
||||
objData.images[transparentDiffuseTextureUrl] = transparentDiffuseTexture;
|
||||
var material = new Material();
|
||||
material.diffuseTexture = transparentDiffuseTextureUrl;
|
||||
boxObjData.materials.Material = material;
|
||||
|
||||
var gltf = createGltf(objData);
|
||||
boxObjData.images[transparentDiffuseTextureUrl] = transparentDiffuseTexture;
|
||||
|
||||
var gltf = createGltf(boxObjData);
|
||||
var kmc = gltf.materials.Material.extensions.KHR_materials_common;
|
||||
|
||||
expect(kmc.values.diffuse).toBe('texture_diffuse');
|
||||
@ -193,13 +199,12 @@ describe('gltf', function() {
|
||||
});
|
||||
|
||||
it('sets material for specular', function() {
|
||||
var objData = clone(boxObjData, true);
|
||||
objData.materials.Material = {
|
||||
specularColor : [0.1, 0.1, 0.2, 1],
|
||||
specularShininess : 0.1
|
||||
};
|
||||
var material = new Material();
|
||||
material.specularColor = [0.1, 0.1, 0.2, 1];
|
||||
material.specularShininess = 0.1;
|
||||
boxObjData.materials.Material = material;
|
||||
|
||||
var gltf = createGltf(objData);
|
||||
var gltf = createGltf(boxObjData);
|
||||
var kmc = gltf.materials.Material.extensions.KHR_materials_common;
|
||||
|
||||
expect(kmc.technique).toBe('PHONG');
|
||||
@ -208,14 +213,15 @@ describe('gltf', function() {
|
||||
});
|
||||
|
||||
it('sets constant material when there are no normals', function() {
|
||||
var objData = clone(boxObjData, true);
|
||||
objData.nodes[0].meshes[0].normals.length = 0;
|
||||
objData.materials.Material = {
|
||||
diffuseColorMap : diffuseTextureUrl
|
||||
};
|
||||
objData.images[diffuseTextureUrl] = diffuseTexture;
|
||||
boxObjData.nodes[0].meshes[0].normals.length = 0;
|
||||
|
||||
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;
|
||||
|
||||
expect(kmc.technique).toBe('CONSTANT');
|
||||
@ -223,70 +229,66 @@ describe('gltf', function() {
|
||||
});
|
||||
|
||||
it('sets default material when texture is missing', function() {
|
||||
var objData = clone(boxObjData, true);
|
||||
objData.materials.Material = {
|
||||
diffuseColorMap : diffuseTextureUrl
|
||||
};
|
||||
var material = new Material();
|
||||
material.diffuseTexture = diffuseTextureUrl;
|
||||
boxObjData.materials.Material = material;
|
||||
|
||||
var gltf = createGltf(objData);
|
||||
var gltf = createGltf(boxObjData);
|
||||
var kmc = gltf.materials.Material.extensions.KHR_materials_common;
|
||||
|
||||
expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 1.0]);
|
||||
});
|
||||
|
||||
it('uses default material (1)', function() {
|
||||
var objData = clone(boxObjData, true);
|
||||
objData.nodes[0].meshes[0].primitives[0].material = undefined;
|
||||
boxObjData.nodes[0].meshes[0].primitives[0].material = undefined;
|
||||
|
||||
// Creates a material called "default"
|
||||
var gltf = createGltf(objData);
|
||||
var gltf = createGltf(boxObjData);
|
||||
expect(gltf.materials.default).toBeDefined();
|
||||
var kmc = gltf.materials.default.extensions.KHR_materials_common;
|
||||
expect(kmc.values.diffuse).toEqual([0.5, 0.5, 0.5, 1.0]);
|
||||
});
|
||||
|
||||
it('uses default material (2)', function() {
|
||||
var objData = clone(boxObjData, true);
|
||||
objData.materials = {};
|
||||
boxObjData.materials = {};
|
||||
|
||||
// Uses the original name of the material
|
||||
var gltf = createGltf(objData);
|
||||
var gltf = createGltf(boxObjData);
|
||||
var kmc = gltf.materials.Material.extensions.KHR_materials_common;
|
||||
|
||||
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
|
||||
var objData = clone(boxObjData, true);
|
||||
objData.nodes.push(clone(objData.nodes[0], true));
|
||||
objData.nodes[1].meshes[0].normals.length = 0;
|
||||
boxObjData.nodes.push(duplicateBoxObjData.nodes[0]);
|
||||
boxObjData.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 kmc2 = gltf.materials.Material_constant.extensions.KHR_materials_common;
|
||||
|
||||
expect(kmc1.technique).toBe('PHONG');
|
||||
expect(kmc2.technique).toBe('CONSTANT');
|
||||
});
|
||||
|
||||
it('handles material used with and without normals (2)', function() {
|
||||
// Now test in a different order
|
||||
objData = clone(boxObjData, true);
|
||||
objData.nodes.push(clone(objData.nodes[0], true));
|
||||
objData.nodes[0].meshes[0].normals.length = 0;
|
||||
boxObjData.nodes.push(duplicateBoxObjData.nodes[0]);
|
||||
boxObjData.nodes[0].meshes[0].normals.length = 0;
|
||||
|
||||
gltf = createGltf(objData);
|
||||
kmc1 = gltf.materials.Material.extensions.KHR_materials_common;
|
||||
kmc2 = gltf.materials.Material_shaded.extensions.KHR_materials_common;
|
||||
var gltf = createGltf(boxObjData);
|
||||
var kmc1 = gltf.materials.Material.extensions.KHR_materials_common;
|
||||
var kmc2 = gltf.materials.Material_shaded.extensions.KHR_materials_common;
|
||||
|
||||
expect(kmc1.technique).toBe('CONSTANT');
|
||||
expect(kmc2.technique).toBe('PHONG');
|
||||
});
|
||||
|
||||
it('runs without normals', function() {
|
||||
var objData = clone(boxObjData, true);
|
||||
objData.nodes[0].meshes[0].normals.length = 0;
|
||||
boxObjData.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;
|
||||
expect(attributes.POSITION).toBeDefined();
|
||||
expect(attributes.NORMAL).toBeUndefined();
|
||||
@ -294,10 +296,9 @@ describe('gltf', function() {
|
||||
});
|
||||
|
||||
it('runs without uvs', function() {
|
||||
var objData = clone(boxObjData, true);
|
||||
objData.nodes[0].meshes[0].uvs.length = 0;
|
||||
boxObjData.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;
|
||||
expect(attributes.POSITION).toBeDefined();
|
||||
expect(attributes.NORMAL).toBeDefined();
|
||||
@ -305,11 +306,10 @@ describe('gltf', function() {
|
||||
});
|
||||
|
||||
it('runs without uvs and normals', function() {
|
||||
var objData = clone(boxObjData, true);
|
||||
objData.nodes[0].meshes[0].normals.length = 0;
|
||||
objData.nodes[0].meshes[0].uvs.length = 0;
|
||||
boxObjData.nodes[0].meshes[0].normals.length = 0;
|
||||
boxObjData.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;
|
||||
expect(attributes.POSITION).toBeDefined();
|
||||
expect(attributes.NORMAL).toBeUndefined();
|
||||
@ -344,13 +344,12 @@ describe('gltf', function() {
|
||||
}
|
||||
|
||||
it('detects need to use uint32 indices', function() {
|
||||
var objData = clone(boxObjData, true);
|
||||
expandObjData(objData, 2731); // Right above 65536 limit
|
||||
var mesh = objData.nodes[0].meshes[0];
|
||||
expandObjData(boxObjData, 2731); // Right above 65536 limit
|
||||
var mesh = boxObjData.nodes[0].meshes[0];
|
||||
var indicesLength = mesh.primitives[0].indices.length;
|
||||
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 indicesAccessor = gltf.accessors[primitive.indices];
|
||||
expect(indicesAccessor.count).toBe(indicesLength);
|
||||
|
@ -1,8 +1,9 @@
|
||||
'use strict';
|
||||
var Cesium = require('cesium');
|
||||
var path = require('path');
|
||||
var loadImage = require('../../lib/image.js');
|
||||
var convert = require('../../lib/convert');
|
||||
var loadImage = require('../../lib/image');
|
||||
|
||||
var clone = Cesium.clone;
|
||||
var WebGLConstants = Cesium.WebGLConstants;
|
||||
|
||||
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 grayscaleImage = 'specs/data/box-complex-material/alpha.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() {
|
||||
it('loads png image', function(done) {
|
||||
expect(loadImage(pngImage)
|
||||
expect(loadImage(pngImage, defaultOptions)
|
||||
.then(function(info) {
|
||||
expect(info.transparent).toBe(false);
|
||||
expect(info.format).toBe(WebGLConstants.RGB);
|
||||
@ -26,7 +27,7 @@ describe('image', function() {
|
||||
});
|
||||
|
||||
it('loads jpg image', function(done) {
|
||||
expect(loadImage(jpgImage)
|
||||
expect(loadImage(jpgImage, defaultOptions)
|
||||
.then(function(info) {
|
||||
expect(info.transparent).toBe(false);
|
||||
expect(info.format).toBe(WebGLConstants.RGB);
|
||||
@ -36,7 +37,7 @@ describe('image', function() {
|
||||
});
|
||||
|
||||
it('loads jpeg image', function(done) {
|
||||
expect(loadImage(jpegImage)
|
||||
expect(loadImage(jpegImage, defaultOptions)
|
||||
.then(function(info) {
|
||||
expect(info.transparent).toBe(false);
|
||||
expect(info.format).toBe(WebGLConstants.RGB);
|
||||
@ -46,7 +47,7 @@ describe('image', function() {
|
||||
});
|
||||
|
||||
it('loads gif image', function(done) {
|
||||
expect(loadImage(gifImage)
|
||||
expect(loadImage(gifImage, defaultOptions)
|
||||
.then(function(info) {
|
||||
expect(info.transparent).toBe(false);
|
||||
expect(info.format).toBe(WebGLConstants.RGB);
|
||||
@ -56,7 +57,7 @@ describe('image', function() {
|
||||
});
|
||||
|
||||
it('loads grayscale image', function(done) {
|
||||
expect(loadImage(grayscaleImage)
|
||||
expect(loadImage(grayscaleImage, defaultOptions)
|
||||
.then(function(info) {
|
||||
expect(info.transparent).toBe(false);
|
||||
expect(info.format).toBe(WebGLConstants.ALPHA);
|
||||
@ -65,30 +66,20 @@ describe('image', function() {
|
||||
}), done).toResolve();
|
||||
});
|
||||
|
||||
it('loads transparent image', function(done) {
|
||||
expect(loadImage(transparentImage)
|
||||
.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)
|
||||
it('loads image with alpha channel', function(done) {
|
||||
expect(loadImage(transparentImage, defaultOptions)
|
||||
.then(function(info) {
|
||||
expect(info.transparent).toBe(false);
|
||||
}), 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();
|
||||
});
|
||||
});
|
||||
|
@ -1,13 +1,12 @@
|
||||
'use strict';
|
||||
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 multipleMaterialsUrl = 'specs/data/box-multiple-materials/box-multiple-materials.mtl';
|
||||
var invalidMaterialUrl = 'invalid.mtl';
|
||||
|
||||
function getImagePath(objPath, relativePath) {
|
||||
return path.normalize(path.join(path.dirname(objPath), relativePath));
|
||||
return path.resolve(path.dirname(objPath), relativePath);
|
||||
}
|
||||
|
||||
describe('mtl', function() {
|
||||
@ -22,10 +21,10 @@ describe('mtl', function() {
|
||||
expect(material.specularColor).toEqual([0.5, 0.5, 0.5, 1.0]);
|
||||
expect(material.specularShininess).toEqual(96.078431);
|
||||
expect(material.alpha).toEqual(0.9);
|
||||
expect(material.ambientColorMap).toEqual(getImagePath(complexMaterialUrl, 'ambient.gif'));
|
||||
expect(material.emissionColorMap).toEqual(getImagePath(complexMaterialUrl, 'emission.jpg'));
|
||||
expect(material.diffuseColorMap).toEqual(getImagePath(complexMaterialUrl, 'diffuse.png'));
|
||||
expect(material.specularColorMap).toEqual(getImagePath(complexMaterialUrl, 'specular.jpeg'));
|
||||
expect(material.ambientTexture).toEqual(getImagePath(complexMaterialUrl, 'ambient.gif'));
|
||||
expect(material.emissionTexture).toEqual(getImagePath(complexMaterialUrl, 'emission.jpg'));
|
||||
expect(material.diffuseTexture).toEqual(getImagePath(complexMaterialUrl, 'diffuse.png'));
|
||||
expect(material.specularTexture).toEqual(getImagePath(complexMaterialUrl, 'specular.jpeg'));
|
||||
expect(material.specularShininessMap).toEqual(getImagePath(complexMaterialUrl, 'shininess.png'));
|
||||
expect(material.normalMap).toEqual(getImagePath(complexMaterialUrl, 'bump.png'));
|
||||
expect(material.alphaMap).toEqual(getImagePath(complexMaterialUrl, 'alpha.png'));
|
||||
|
@ -2,8 +2,10 @@
|
||||
var Cesium = require('cesium');
|
||||
var path = require('path');
|
||||
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 objUrl = 'specs/data/box/box.obj';
|
||||
@ -54,12 +56,14 @@ function getPrimitives(data) {
|
||||
}
|
||||
|
||||
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() {
|
||||
it('loads obj with positions, normals, and uvs', function(done) {
|
||||
expect(loadObj(objUrl)
|
||||
expect(loadObj(objUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
var images = data.images;
|
||||
var materials = data.materials;
|
||||
@ -88,7 +92,7 @@ describe('obj', function() {
|
||||
});
|
||||
|
||||
it('loads obj with normals', function(done) {
|
||||
expect(loadObj(objNormalsUrl)
|
||||
expect(loadObj(objNormalsUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
var mesh = getMeshes(data)[0];
|
||||
expect(mesh.positions.length / 3).toBe(24);
|
||||
@ -98,7 +102,7 @@ describe('obj', function() {
|
||||
});
|
||||
|
||||
it('loads obj with uvs', function(done) {
|
||||
expect(loadObj(objUvsUrl)
|
||||
expect(loadObj(objUvsUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
var mesh = getMeshes(data)[0];
|
||||
expect(mesh.positions.length / 3).toBe(20);
|
||||
@ -109,8 +113,8 @@ describe('obj', function() {
|
||||
|
||||
it('loads obj with negative indices', function(done) {
|
||||
expect(Promise.all([
|
||||
loadObj(objPositionsOnlyUrl),
|
||||
loadObj(objNegativeIndicesUrl)
|
||||
loadObj(objPositionsOnlyUrl, defaultOptions),
|
||||
loadObj(objNegativeIndicesUrl, defaultOptions)
|
||||
])
|
||||
.then(function(results) {
|
||||
var positionsReference = getMeshes(results[0])[0].positions.toFloatBuffer();
|
||||
@ -120,7 +124,7 @@ describe('obj', function() {
|
||||
});
|
||||
|
||||
it('loads obj with triangle faces', function(done) {
|
||||
expect(loadObj(objTrianglesUrl)
|
||||
expect(loadObj(objTrianglesUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
var mesh = getMeshes(data)[0];
|
||||
var primitive = getPrimitives(data)[0];
|
||||
@ -130,7 +134,7 @@ describe('obj', function() {
|
||||
});
|
||||
|
||||
it('loads obj with objects', function(done) {
|
||||
expect(loadObj(objObjectsUrl)
|
||||
expect(loadObj(objObjectsUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
var nodes = data.nodes;
|
||||
expect(nodes.length).toBe(3);
|
||||
@ -147,7 +151,7 @@ describe('obj', function() {
|
||||
});
|
||||
|
||||
it('loads obj with groups', function(done) {
|
||||
expect(loadObj(objGroupsUrl)
|
||||
expect(loadObj(objGroupsUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
var nodes = data.nodes;
|
||||
expect(nodes.length).toBe(3);
|
||||
@ -164,7 +168,7 @@ describe('obj', function() {
|
||||
});
|
||||
|
||||
it('loads obj with objects and groups', function(done) {
|
||||
expect(loadObj(objObjectsGroupsUrl)
|
||||
expect(loadObj(objObjectsGroupsUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
var nodes = data.nodes;
|
||||
expect(nodes.length).toBe(3);
|
||||
@ -187,7 +191,7 @@ describe('obj', function() {
|
||||
});
|
||||
|
||||
it('loads obj with usemtl only', function(done) {
|
||||
expect(loadObj(objUsemtlUrl)
|
||||
expect(loadObj(objUsemtlUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
var nodes = data.nodes;
|
||||
expect(nodes.length).toBe(1);
|
||||
@ -206,7 +210,7 @@ describe('obj', function() {
|
||||
});
|
||||
|
||||
it('loads obj with no materials', function(done) {
|
||||
expect(loadObj(objNoMaterialsUrl)
|
||||
expect(loadObj(objNoMaterialsUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
var nodes = data.nodes;
|
||||
expect(nodes.length).toBe(1);
|
||||
@ -219,7 +223,7 @@ describe('obj', function() {
|
||||
|
||||
it('loads obj with multiple materials', function(done) {
|
||||
// The usemtl markers are interleaved, but should condense to just three primitives
|
||||
expect(loadObj(objMultipleMaterialsUrl)
|
||||
expect(loadObj(objMultipleMaterialsUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
var nodes = data.nodes;
|
||||
expect(nodes.length).toBe(1);
|
||||
@ -239,7 +243,7 @@ describe('obj', function() {
|
||||
it('loads obj uncleaned', function(done) {
|
||||
// Obj with extraneous o, g, and usemtl lines
|
||||
// Also tests handling of o and g lines with the same names
|
||||
expect(loadObj(objUncleanedUrl)
|
||||
expect(loadObj(objUncleanedUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
var nodes = data.nodes;
|
||||
var meshes = getMeshes(data);
|
||||
@ -255,7 +259,7 @@ describe('obj', function() {
|
||||
});
|
||||
|
||||
it('loads obj with multiple mtllibs', function(done) {
|
||||
expect(loadObj(objMtllibUrl)
|
||||
expect(loadObj(objMtllibUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
var materials = data.materials;
|
||||
expect(Object.keys(materials).length).toBe(3);
|
||||
@ -267,7 +271,7 @@ describe('obj', function() {
|
||||
|
||||
it('loads obj with missing mtllib', function(done) {
|
||||
spyOn(console, 'log');
|
||||
expect(loadObj(objMissingMtllibUrl)
|
||||
expect(loadObj(objMissingMtllibUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
expect(data.materials).toEqual({});
|
||||
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) {
|
||||
expect(loadObj(objExternalResourcesUrl)
|
||||
expect(loadObj(objExternalResourcesUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
var imagePath = getImagePath(objTexturedUrl, 'cesium.png');
|
||||
expect(data.images[imagePath]).toBeDefined();
|
||||
expect(data.materials.MaterialTextured.diffuseColorMap).toEqual(imagePath);
|
||||
expect(data.materials.MaterialTextured.diffuseTexture).toEqual(imagePath);
|
||||
}), done).toResolve();
|
||||
});
|
||||
|
||||
it('does not load resources outside of the obj directory when secure is true', function(done) {
|
||||
spyOn(console, 'log');
|
||||
var options = {
|
||||
secure : true
|
||||
};
|
||||
|
||||
var options = clone(defaultOptions);
|
||||
options.secure = true;
|
||||
|
||||
expect(loadObj(objExternalResourcesUrl, options)
|
||||
.then(function(data) {
|
||||
var imagePath = getImagePath(objMissingTextureUrl, 'cesium.png');
|
||||
@ -300,36 +305,36 @@ describe('obj', function() {
|
||||
});
|
||||
|
||||
it('loads obj with texture', function(done) {
|
||||
expect(loadObj(objTexturedUrl)
|
||||
expect(loadObj(objTexturedUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
var imagePath = getImagePath(objTexturedUrl, 'cesium.png');
|
||||
expect(data.images[imagePath]).toBeDefined();
|
||||
expect(data.materials.Material.diffuseColorMap).toEqual(imagePath);
|
||||
expect(data.materials.Material.diffuseTexture).toEqual(imagePath);
|
||||
}), done).toResolve();
|
||||
});
|
||||
|
||||
it('loads obj with missing texture', function(done) {
|
||||
spyOn(console, 'log');
|
||||
expect(loadObj(objMissingTextureUrl)
|
||||
expect(loadObj(objMissingTextureUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
var imagePath = getImagePath(objMissingTextureUrl, 'cesium.png');
|
||||
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);
|
||||
}), done).toResolve();
|
||||
});
|
||||
|
||||
it('loads obj with subdirectories', function(done) {
|
||||
expect(loadObj(objSubdirectoriesUrl)
|
||||
expect(loadObj(objSubdirectoriesUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
var imagePath = getImagePath(objSubdirectoriesUrl, path.join('materials', 'images', 'cesium.png'));
|
||||
expect(data.images[imagePath]).toBeDefined();
|
||||
expect(data.materials.Material.diffuseColorMap).toEqual(imagePath);
|
||||
expect(data.materials.Material.diffuseTexture).toEqual(imagePath);
|
||||
}), done).toResolve();
|
||||
});
|
||||
|
||||
it('loads obj with complex material', function(done) {
|
||||
expect(loadObj(objComplexMaterialUrl)
|
||||
expect(loadObj(objComplexMaterialUrl, defaultOptions)
|
||||
.then(function(data) {
|
||||
var images = data.images;
|
||||
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) {
|
||||
expect(loadObj(objInvalidContentsUrl), done).toRejectWith(RuntimeError);
|
||||
expect(loadObj(objInvalidContentsUrl, defaultOptions), done).toRejectWith(RuntimeError);
|
||||
});
|
||||
|
||||
it('throw when reading invalid file', function(done) {
|
||||
expect(loadObj(objInvalidUrl), done).toRejectWith(Error);
|
||||
expect(loadObj(objInvalidUrl, defaultOptions), done).toRejectWith(Error);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user