diff --git a/.gitignore b/.gitignore index ad27f7b..b04cad6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,14 +2,16 @@ node_modules npm-debug.log +# TypeScript definitions +typings + # WebStorm user-specific .idea/workspace.xml .idea/tasks.xml -# TypeScript definitions -typings - # Generate data -test coverage +doc +output +test *.tgz diff --git a/.idea/modules.xml b/.idea/modules.xml index eb8cfab..662b990 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/.idea/OBJ2GLTF.iml b/.idea/obj2gltf.iml similarity index 100% rename from .idea/OBJ2GLTF.iml rename to .idea/obj2gltf.iml diff --git a/.npmignore b/.npmignore index 1fe05bd..6e6218a 100644 --- a/.npmignore +++ b/.npmignore @@ -2,6 +2,7 @@ /doc /specs /test +/output /typings /coverage .jshintrc diff --git a/.travis.yml b/.travis.yml index a73b51f..72dada3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,3 +4,8 @@ node_js: script: - npm run jsHint -- --failTaskOnError - npm run test -- --failTaskOnError --suppressPassed + +after_success: + ## We only need to run coveralls for one node version (doesn't matter which one). + ## We also ignore publishing failures, since restarting an existing travis build would otherwise break. + - if [$(node --version) == v6*]; then npm run coverage && npm run coveralls; fi diff --git a/LICENSE.md b/LICENSE.md index 8938747..82de349 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -11,53 +11,29 @@ Third-Party Code obj2gltf includes the following third-party code. -### async +### bluebird -https://www.npmjs.com/package/async - -> Copyright (c) 2010-2016 Caolan McMahon +> The MIT License (MIT) +> +> Copyright (c) 2013-2015 Petka Antonov > > Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: > > The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +> all copies or substantial portions of the Software. > > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -### byline - -https://www.npmjs.com/package/byline - -> node-byline (C) 2011-2015 John Hewson -> -> Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to -deal in the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: -> -> The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. -> -> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -IN THE SOFTWARE. +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. ### Cesium @@ -73,6 +49,35 @@ http://cesiumjs.org/ See https://github.com/AnalyticalGraphicsInc/cesium/blob/master/LICENSE.md +### event-stream + +https://www.npmjs.com/package/event-stream + +> The MIT License (MIT) +> +> Copyright (c) 2011 Dominic Tarr +> +> Permission is hereby granted, free of charge, +> to any person obtaining a copy of this software and +> associated documentation files (the "Software"), to +> deal in the Software without restriction, including +> without limitation the rights to use, copy, modify, +> merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom +> the Software is furnished to do so, +> subject to the following conditions: +> +> The above copyright notice and this permission notice +> shall be included in all copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +> ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ### fs-extra https://www.npmjs.com/package/fs-extra @@ -82,16 +87,16 @@ https://www.npmjs.com/package/fs-extra > Copyright (c) 2011-2016 JP Richardson > > Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files -(the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +> (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, > merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is > furnished to do so, subject to the following conditions: > > The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - +> > THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS -OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +> WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +> OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +> ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ### gltf-pipeline @@ -112,24 +117,24 @@ https://www.npmjs.com/package/yargs The command-line tool uses yargs. > Copyright 2010 James Halliday (mail@substack.net) -Modified work Copyright 2014 Contributors (ben@npmjs.com) +> Modified work Copyright 2014 Contributors (ben@npmjs.com) > > This project is free software released under the MIT/X11 license: > > Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: > > The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +> all copies or substantial portions of the Software. > > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +> THE SOFTWARE. diff --git a/README.md b/README.md index f1e7784..d9818fc 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Using obj2gltf as a library: var obj2gltf = require('obj2gltf'); var convert = obj2gltf.convert; var options = { - embedImage : false // Don't embed image in the converted glTF + separateTextures : true // Don't embed textures in the converted glTF } convert('model.obj', 'model.gltf', options) .then(function() { @@ -30,20 +30,24 @@ Using obj2gltf as a command-line tool: `node bin/obj2gltf.js -i model.obj -o model.gltf -s` - ## Usage ###Command line flags: |Flag|Description|Required| |----|-----------|--------| -|`-i`|Path to the input OBJ file.| :white_check_mark: Yes| -|`-o`|Directory or filename for the exported glTF file.|No| -|`-b`|Output binary glTF.|No, default `false`| +|`-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/animation 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`| |`--ao`|Apply ambient occlusion to the converted model.|No, default `false`| -|`-h`|Display help|No| +|`--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`| ## Contributions @@ -53,5 +57,5 @@ Pull requests are appreciated. Please use the same [Contributor License Agreeme Developed by the Cesium team.

- +

diff --git a/bin/obj2gltf.js b/bin/obj2gltf.js index caeb576..6cfaf40 100644 --- a/bin/obj2gltf.js +++ b/bin/obj2gltf.js @@ -1,53 +1,116 @@ #!/usr/bin/env node -"use strict"; -var argv = require('yargs').argv; +'use strict'; var Cesium = require('cesium'); -var defined = Cesium.defined; -var defaultValue = Cesium.defaultValue; +var path = require('path'); +var yargs = require('yargs'); var convert = require('../lib/convert'); -if (process.argv.length < 3 || defined(argv.h) || defined(argv.help)) { - console.log('Usage: ./bin/obj2gltf.js [INPUT] [OPTIONS]'); - console.log(' -i, --input Path to obj file'); - console.log(' -o, --output Directory or filename for the exported glTF file'); - console.log(' -b, --binary Output binary glTF'); - console.log(' -s --separate Writes out separate geometry/animation data files, shader files, and textures instead of embedding them in the glTF file.'); - console.log(' -t --separateImage Write out separate textures only.'); - console.log(' -c --compress Quantize positions, compress texture coordinates, and oct-encode normals.'); - console.log(' -h, --help Display this help'); - console.log(' --ao Apply ambient occlusion to the converted model'); - console.log(' --cesium Optimize the glTF for Cesium by using the sun as a default light source.'); - process.exit(0); +var defaultValue = Cesium.defaultValue; +var defined = Cesium.defined; + +var args = process.argv; +args = args.slice(2, args.length); + +var argv = yargs + .usage('Usage: node $0 -i inputPath -o outputPath') + .example('node $0 -i ./specs/data/box/box.obj -o box.gltf') + .help('h') + .alias('h', 'help') + .options({ + 'input': { + alias: 'i', + describe: 'Path to the obj file.', + type: 'string', + normalize: true + }, + 'output': { + alias: 'o', + describe: 'Path of the converted glTF file.', + type: 'string', + normalize: true + }, + 'binary': { + alias: 'b', + describe: 'Save as binary glTF.', + type: 'boolean', + default: false + }, + 'separate': { + alias: 's', + describe: 'Write separate geometry/animation data files, shader files, and textures instead of embedding them in the glTF.', + type: 'boolean', + default: false + }, + 'separateTexture': { + alias: 't', + describe: 'Write out separate textures only.', + type: 'boolean', + default: false + }, + 'compress': { + alias: 'c', + describe: 'Quantize positions, compress texture coordinates, and oct-encode normals.', + type: 'boolean', + default: false + }, + 'optimize': { + alias: 'z', + describe: 'Use the optimization stages in the glTF pipeline.', + type: 'boolean', + default: false + }, + 'cesium': { + describe: 'Optimize the glTF for Cesium by using the sun as a default light source.', + type: 'boolean', + default: false + }, + 'generateNormals': { + alias: 'n', + describe: 'Generate normals if they are missing.', + type: 'boolean', + default: false + }, + 'ao': { + describe: 'Apply ambient occlusion to the converted model.', + type: 'boolean', + default: false + }, + '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 + } + }).parse(args); + +var objPath = defaultValue(argv.i, argv._[0]); +var gltfPath = defaultValue(argv.o, argv._[1]); + +if (!defined(objPath)) { + yargs.showHelp(); + return; } -var objFile = defaultValue(argv._[0], defaultValue(argv.i, argv.input)); -var outputPath = defaultValue(argv._[1], defaultValue(argv.o, argv.output)); -var binary = defaultValue(defaultValue(argv.b, argv.binary), false); -var separate = defaultValue(defaultValue(argv.s, argv.separate), false); -var separateImage = defaultValue(defaultValue(argv.t, argv.separateImage), false); -var compress = defaultValue(defaultValue(argv.c, argv.compress), false); -var ao = defaultValue(argv.ao, false); -var optimizeForCesium = defaultValue(argv.cesium, false); - -if (!defined(objFile)) { - throw new Error('-i or --input argument is required. See --help for details.'); +if (!defined(gltfPath)) { + var extension = argv.b ? '.glb' : '.gltf'; + var modelName = path.basename(objPath, path.extname(objPath)); + gltfPath = path.join(path.dirname(objPath), modelName + extension); } +var options = { + binary : argv.b, + separate : argv.s, + separateTextures : argv.t, + compress : argv.c, + optimize : argv.z, + generateNormals : argv.n, + ao : argv.ao, + optimizeForCesium : argv.cesium, + bypassPipeline : argv.bypassPipeline +}; + console.time('Total'); -var options = { - binary : binary, - embed : !separate, - embedImage : !separateImage, - compress : compress, - ao : ao, - optimizeForCesium : optimizeForCesium -}; - -convert(objFile, outputPath, options) +convert(objPath, gltfPath, options) .then(function() { console.timeEnd('Total'); - }) - .catch(function(err) { - console.log(err); }); diff --git a/gulpfile.js b/gulpfile.js index f479227..8d12058 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -6,7 +6,7 @@ var fsExtra = require('fs-extra'); var gulp = require('gulp'); var gulpJshint = require('gulp-jshint'); var Jasmine = require('jasmine'); -var JasmineSpecReporter = require('jasmine-spec-reporter'); +var JasmineSpecReporter = require('jasmine-spec-reporter').SpecReporter; var open = require('open'); var path = require('path'); var yargs = require('yargs'); @@ -20,8 +20,8 @@ var environmentSeparator = process.platform === 'win32' ? ';' : ':'; var nodeBinaries = path.join(__dirname, 'node_modules', '.bin'); process.env.PATH += environmentSeparator + nodeBinaries; -var jsHintFiles = ['**/*.js', '!node_modules/**', '!coverage/**']; -var specFiles = ['**/*.js', '!node_modules/**', '!coverage/**']; +var jsHintFiles = ['**/*.js', '!node_modules/**', '!coverage/**', '!doc/**']; +var specFiles = ['**/*.js', '!node_modules/**', '!coverage/**', '!doc/**', '!bin/**']; gulp.task('jsHint', function () { var stream = gulp.src(jsHintFiles) @@ -53,8 +53,8 @@ gulp.task('test', function (done) { gulp.task('test-watch', function () { gulp.watch(specFiles).on('change', function () { - //We can't simply depend on the test task because Jasmine - //does not like being run multiple times in the same process. + // We can't simply depend on the test task because Jasmine + // does not like being run multiple times in the same process. try { child_process.execSync('jasmine JASMINE_CONFIG_PATH=specs/jasmine.json', { stdio: [process.stdin, process.stdout, process.stderr] @@ -71,7 +71,7 @@ gulp.task('coverage', function () { ' cover' + ' --include-all-sources' + ' --dir coverage' + - ' -x "specs/** coverage/** index.js gulpfile.js"' + + ' -x "specs/**" -x "coverage/**" -x "doc/**" -x "bin/**" -x "index.js" -x "gulpfile.js"' + ' node_modules/jasmine/bin/jasmine.js' + ' JASMINE_CONFIG_PATH=specs/jasmine.json', { stdio: [process.stdin, process.stdout, process.stderr] diff --git a/lib/ArrayStorage.js b/lib/ArrayStorage.js new file mode 100644 index 0000000..c97fd43 --- /dev/null +++ b/lib/ArrayStorage.js @@ -0,0 +1,107 @@ +'use strict'; +var Cesium = require('cesium'); + +var ComponentDatatype = Cesium.ComponentDatatype; + +module.exports = ArrayStorage; + +var initialLength = 1024; // 2^10 +var doublingThreshold = 33554432; // 2^25 (~134 MB for a Float32Array) +var fixedExpansionLength = 33554432; // 2^25 (~134 MB for a Float32Array) + +/** + * Provides expandable typed array storage for geometry data. This is preferable to JS arrays which are + * stored with double precision. The resizing mechanism is similar to std::vector. + * + * @param {ComponentDatatype} componentDatatype The data type. + * @constructor + * + * @private + */ +function ArrayStorage(componentDatatype) { + this.componentDatatype = componentDatatype; + this.typedArray = ComponentDatatype.createTypedArray(componentDatatype, 0); + this.length = 0; +} + +function resize(storage, length) { + var typedArray = ComponentDatatype.createTypedArray(storage.componentDatatype, length); + typedArray.set(storage.typedArray); + storage.typedArray = typedArray; +} + +ArrayStorage.prototype.push = function(value) { + var length = this.length; + var typedArrayLength = this.typedArray.length; + + if (length === 0) { + resize(this, initialLength); + } else if (length === typedArrayLength) { + if (length < doublingThreshold) { + resize(this, typedArrayLength * 2); + } else { + resize(this, typedArrayLength + fixedExpansionLength); + } + } + + this.typedArray[this.length++] = value; +}; + +ArrayStorage.prototype.get = function(index) { + return this.typedArray[index]; +}; + +var sizeOfUint16 = 2; +var sizeOfUint32 = 4; +var sizeOfFloat = 4; + +ArrayStorage.prototype.toUint16Buffer = function() { + var length = this.length; + var typedArray = this.typedArray; + var paddedLength = length + ((length % 2 === 0) ? 0 : 1); // Round to next multiple of 2 + var buffer = Buffer.alloc(paddedLength * sizeOfUint16); + for (var i = 0; i < length; ++i) { + buffer.writeUInt16LE(typedArray[i], i * sizeOfUint16); + } + return buffer; +}; + +ArrayStorage.prototype.toUint32Buffer = function() { + var length = this.length; + var typedArray = this.typedArray; + var buffer = Buffer.alloc(length * sizeOfUint32); + for (var i = 0; i < length; ++i) { + buffer.writeUInt32LE(typedArray[i], i * sizeOfUint32); + } + return buffer; +}; + +ArrayStorage.prototype.toFloatBuffer = function() { + var length = this.length; + var typedArray = this.typedArray; + var buffer = Buffer.alloc(length * sizeOfFloat); + for (var i = 0; i < length; ++i) { + buffer.writeFloatLE(typedArray[i], i * sizeOfFloat); + } + return buffer; +}; + +ArrayStorage.prototype.getMinMax = function(components) { + var length = this.length; + var typedArray = this.typedArray; + var count = length / components; + var min = new Array(components).fill(Number.POSITIVE_INFINITY); + var max = new Array(components).fill(Number.NEGATIVE_INFINITY); + for (var i = 0; i < count; ++i) { + for (var j = 0; j < components; ++j) { + var index = i * components + j; + var value = typedArray[index]; + min[j] = Math.min(min[j], value); + max[j] = Math.max(max[j], value); + } + } + return { + min : min, + max : max + }; +}; diff --git a/lib/clone.js b/lib/clone.js new file mode 100644 index 0000000..d93e059 --- /dev/null +++ b/lib/clone.js @@ -0,0 +1,54 @@ +'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; +} diff --git a/lib/convert.js b/lib/convert.js index 8a135d8..6088198 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -1,60 +1,121 @@ -"use strict"; -var path = require('path'); -var GltfPipeline = require('gltf-pipeline').Pipeline; -var parseObj = require('./obj'); -var createGltf = require('./gltf'); +'use strict'; var Cesium = require('cesium'); -var defined = Cesium.defined; +var fsExtra = require('fs-extra'); +var GltfPipeline = require('gltf-pipeline').Pipeline; +var path = require('path'); +var Promise = require('bluebird'); +var createGltf = require('./gltf'); +var loadObj = require('./obj'); + +var fxExtraOutputFile = Promise.promisify(fsExtra.outputFile); +var fsExtraOutputJson = Promise.promisify(fsExtra.outputJson); + var defaultValue = Cesium.defaultValue; +var defined = Cesium.defined; +var DeveloperError = Cesium.DeveloperError; module.exports = convert; -function convert(objFile, outputPath, options) { - options = defaultValue(options, {}); +/** + * Converts an obj file to a glTF file. + * + * @param {String} objPath Path to the obj file. + * @param {String} gltfPath Path of the converted glTF file. + * @param {Object} [options] An object with the following properties: + * @param {Boolean} [options.binary=false] Save as binary glTF. + * @param {Boolean} [options.separate=false] Writes out separate geometry/animation 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.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.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. + */ + +function convert(objPath, gltfPath, options) { + options = defaultValue(options, defaultValue.EMPTY_OBJECT); var binary = defaultValue(options.binary, false); - var embed = defaultValue(options.embed, true); - var embedImage = defaultValue(options.embedImage, true); + var separate = defaultValue(options.separate, false); + var separateTextures = defaultValue(options.separateTextures, false); var compress = defaultValue(options.compress, false); - var ao = defaultValue(options.ao, 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 textureCompressionOptions = options.textureCompressionOptions; + var bypassPipeline = defaultValue(options.bypassPipeline, false); - if (!defined(objFile)) { - throw new Error('objFile is required'); + if (!defined(objPath)) { + throw new DeveloperError('objPath is required'); } - if (!defined(outputPath)) { - outputPath = path.dirname(objFile); + if (!defined(gltfPath)) { + throw new DeveloperError('gltfPath is required'); } - var inputPath = path.dirname(objFile); - var modelName = path.basename(objFile, '.obj'); - - var extension = path.extname(outputPath); - if (extension !== '') { - modelName = path.basename(outputPath, extension); - outputPath = path.dirname(outputPath); + var basePath = path.dirname(objPath); + var modelName = path.basename(objPath, path.extname(objPath)); + var extension = path.extname(gltfPath); + if (extension === '.glb') { + binary = true; } + gltfPath = path.join(path.dirname(gltfPath), modelName + extension); - extension = binary ? '.glb' : '.gltf'; - var gltfFile = path.join(outputPath, modelName + extension); + var aoOptions = ao ? {} : undefined; - return parseObj(objFile, inputPath) - .then(function(data) { - return createGltf(data, inputPath, modelName); + var pipelineOptions = { + createDirectory : false, + basePath : basePath, + binary : binary, + embed : !separate, + embedImage : !separate && !separateTextures, + quantize : compress, + compressTextureCoordinates : compress, + encodeNormals : compress, + preserve : !optimize, + optimizeForCesium : optimizeForCesium, + smoothNormals : generateNormals, + aoOptions : aoOptions, + textureCompressionOptions : textureCompressionOptions + }; + + return loadObj(objPath) + .then(function(objData) { + return createGltf(objData); }) .then(function(gltf) { - var aoOptions = ao ? {} : undefined; - var options = { - binary: binary, - embed: embed, - embedImage: embedImage, - encodeNormals: compress, - quantize: compress, - aoOptions: aoOptions, - optimizeForCesium : optimizeForCesium, - createDirectory: false, - basePath: inputPath - }; - return GltfPipeline.processJSONToDisk(gltf, gltfFile, options); + return saveExternalBuffer(gltf, gltfPath); + }) + .then(function(gltf) { + if (bypassPipeline) { + return convert._outputJson(gltfPath, gltf); + } else { + return GltfPipeline.processJSONToDisk(gltf, gltfPath, pipelineOptions); + } + }); +} + +/** + * Exposed for testing + * + * @private + */ +convert._outputJson = fsExtraOutputJson; + +function saveExternalBuffer(gltf, gltfPath) { + var buffer = gltf.buffers[Object.keys(gltf.buffers)[0]]; + if (defined(buffer.uri)) { + return Promise.resolve(gltf); + } + + var bufferName = path.basename(gltfPath, path.extname(gltfPath)); + var bufferUri = bufferName + '.bin'; + buffer.uri = bufferUri; + var bufferPath = path.join(path.dirname(gltfPath), bufferUri); + return fxExtraOutputFile(bufferPath, buffer) + .then(function() { + return gltf; }); } diff --git a/lib/gltf.js b/lib/gltf.js index 4a79303..c0b0189 100644 --- a/lib/gltf.js +++ b/lib/gltf.js @@ -1,148 +1,37 @@ -"use strict"; +'use strict'; var Cesium = require('cesium'); -var Promise = require('bluebird'); -var fs = require('fs-extra'); var path = require('path'); var defined = Cesium.defined; var defaultValue = Cesium.defaultValue; var WebGLConstants = Cesium.WebGLConstants; -var fsWriteFile = Promise.promisify(fs.writeFile); - module.exports = createGltf; -function createGltf(data, inputPath, modelName) { - var vertexCount = data.vertexCount; - var vertexArray = data.vertexArray; - var positionMin = data.positionMin; - var positionMax = data.positionMax; - var hasUVs = data.hasUVs; - var hasNormals = data.hasNormals; - var materialGroups = data.materialGroups; - var materials = data.materials; - var images = data.images; - - var i, j, name; - - var sizeOfFloat32 = 4; - var sizeOfUint32 = 4; - var sizeOfUint16 = 2; - - var indexComponentType; - var indexComponentSize; - - // Reserve the 65535 index for primitive restart - if (vertexCount < 65535) { - indexComponentType = WebGLConstants.UNSIGNED_SHORT; - indexComponentSize = sizeOfUint16; - } else { - indexComponentType = WebGLConstants.UNSIGNED_INT; - indexComponentSize = sizeOfUint32; - } - - // Create primitives - var primitives = []; - var indexArrayLength = 0; - var indexArray; - var indexCount; - for (name in materialGroups) { - if (materialGroups.hasOwnProperty(name)) { - indexArray = materialGroups[name]; - indexCount = indexArray.length; - primitives.push({ - indexArray : indexArray, - indexOffset : indexArrayLength, - indexCount : indexCount, - material : name - }); - indexArrayLength += indexCount; - } - } - - // Create buffer to store vertex and index data - var indexArrayByteLength = indexArrayLength * indexComponentSize; - var vertexArrayLength = vertexArray.length; // In floats - var vertexArrayByteLength = vertexArrayLength * sizeOfFloat32; - var bufferByteLength = vertexArrayByteLength + indexArrayByteLength; - var buffer = new Buffer(bufferByteLength); - - // Write vertex data - var byteOffset = 0; - for (i = 0; i < vertexArrayLength; ++i) { - buffer.writeFloatLE(vertexArray[i], byteOffset); - byteOffset += sizeOfFloat32; - } - - // Write index data - var primitivesLength = primitives.length; - for (i = 0; i < primitivesLength; ++i) { - indexArray = primitives[i].indexArray; - indexCount = indexArray.length; - for (j = 0; j < indexCount; ++j) { - if (indexComponentSize === sizeOfUint16) { - buffer.writeUInt16LE(indexArray[j], byteOffset); - } else { - buffer.writeUInt32LE(indexArray[j], byteOffset); - } - byteOffset += indexComponentSize; - } - } - - var positionByteOffset = 0; - var normalByteOffset = 0; - var uvByteOffset = 0; - var vertexByteStride = 0; - - if (hasNormals && hasUVs) { - normalByteOffset = sizeOfFloat32 * 3; - uvByteOffset = sizeOfFloat32 * 6; - vertexByteStride = sizeOfFloat32 * 8; - } else if (hasNormals && !hasUVs) { - normalByteOffset = sizeOfFloat32 * 3; - vertexByteStride = sizeOfFloat32 * 6; - } else if (!hasNormals && hasUVs) { - uvByteOffset = sizeOfFloat32 * 3; - vertexByteStride = sizeOfFloat32 * 5; - } else if (!hasNormals && !hasUVs) { - vertexByteStride = sizeOfFloat32 * 3; - } - - var bufferId = modelName + '_buffer'; - var bufferViewVertexId = 'bufferView_vertex'; - var bufferViewIndexId = 'bufferView_index'; - var accessorPositionId = 'accessor_position'; - var accessorUVId = 'accessor_uv'; - var accessorNormalId = 'accessor_normal'; - var meshId = 'mesh_' + modelName; - var sceneId = 'scene_' + modelName; - var nodeId = 'node_' + modelName; - var samplerId = 'sampler_0'; - - function getAccessorIndexId(i) { - return 'accessor_index_' + i; - } - - function getMaterialId(material) { - return 'material_' + material; - } - - function getTextureId(image) { - if (!defined(image)) { - return undefined; - } - return 'texture_' + path.basename(image).substr(0, image.lastIndexOf('.')); - } - - function getImageId(image) { - return path.basename(image, path.extname(image)); - } +/** + * Create a glTF from obj data. + * + * @param {Object} objData Output of obj.js, containing an array of nodes containing geometry information, materials, and images. + * @returns {Object} A glTF asset with the KHR_materials_common extension. + * + * @private + */ +function createGltf(objData) { + var nodes = objData.nodes; + var materials = objData.materials; + var images = objData.images; + var sceneId = 'scene'; + var samplerId = 'sampler'; + var bufferId = 'buffer'; + var vertexBufferViewId = 'bufferView_vertex'; + var indexBufferViewId = 'bufferView_index'; var gltf = { accessors : {}, asset : {}, buffers : {}, bufferViews : {}, + extensionsUsed : ['KHR_materials_common'], images : {}, materials : {}, meshes : {}, @@ -154,174 +43,97 @@ function createGltf(data, inputPath, modelName) { }; gltf.asset = { - "generator": "OBJ2GLTF", - "premultipliedAlpha": true, - "profile": { - "api": "WebGL", - "version": "1.0" + generator : 'obj2gltf', + profile : { + api : 'WebGL', + version : '1.0' }, - "version": 1 + version: '1.0' }; gltf.scenes[sceneId] = { - nodes : [nodeId] + nodes : [] }; - gltf.nodes[nodeId] = { - children : [], - matrix : [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], - meshes : [meshId], - name : modelName - }; - - gltf.samplers[samplerId] = {}; // Use default values - - var bufferSeparate = false; - var bufferUri; - if (buffer.length > 201326580) { - // toString fails for buffers larger than ~192MB. Instead save the buffer to a .bin file. - // Source: https://github.com/nodejs/node/issues/4266 - bufferSeparate = true; - bufferUri = modelName + '.bin'; - } else { - bufferUri = 'data:application/octet-stream;base64,' + buffer.toString('base64'); + function getImageId(imagePath) { + return path.basename(imagePath, path.extname(imagePath)); } - gltf.buffers[bufferId] = { - byteLength : bufferByteLength, - type : 'arraybuffer', - uri : bufferUri - }; - - gltf.bufferViews[bufferViewVertexId] = { - buffer : bufferId, - byteLength : vertexArrayByteLength, - byteOffset : 0, - target : WebGLConstants.ARRAY_BUFFER - }; - gltf.bufferViews[bufferViewIndexId] = { - buffer : bufferId, - byteLength : indexArrayByteLength, - byteOffset : vertexArrayByteLength, - target : WebGLConstants.ELEMENT_ARRAY_BUFFER - }; - - for (i = 0; i < primitivesLength; ++i) { - var primitive = primitives[i]; - gltf.accessors[getAccessorIndexId(i)] = { - bufferView : bufferViewIndexId, - byteOffset : primitive.indexOffset * indexComponentSize, - byteStride : 0, - componentType : indexComponentType, - count : primitive.indexCount, - type : 'SCALAR' - }; - } - - gltf.accessors[accessorPositionId] = { - bufferView : bufferViewVertexId, - byteOffset : positionByteOffset, - byteStride : vertexByteStride, - componentType : WebGLConstants.FLOAT, - count : vertexCount, - min : positionMin, - max : positionMax, - type : 'VEC3' - }; - - if (hasNormals) { - gltf.accessors[accessorNormalId] = { - bufferView : bufferViewVertexId, - byteOffset : normalByteOffset, - byteStride : vertexByteStride, - componentType : WebGLConstants.FLOAT, - count : vertexCount, - type : 'VEC3' - }; - } - - if (hasUVs) { - gltf.accessors[accessorUVId] = { - bufferView : bufferViewVertexId, - byteOffset : uvByteOffset, - byteStride : vertexByteStride, - componentType : WebGLConstants.FLOAT, - count : vertexCount, - type : 'VEC2' - }; - } - - var gltfPrimitives = []; - gltf.meshes[meshId] = { - name : modelName, - primitives : gltfPrimitives - }; - - var gltfAttributes = {}; - gltfAttributes.POSITION = accessorPositionId; - if (hasNormals) { - gltfAttributes.NORMAL = accessorNormalId; - } - if (hasUVs) { - gltfAttributes.TEXCOORD_0 = accessorUVId; - } - - for (i = 0; i < primitivesLength; ++i) { - gltfPrimitives.push({ - attributes : gltfAttributes, - indices : getAccessorIndexId(i), - material : getMaterialId(primitives[i].material), - mode : WebGLConstants.TRIANGLES - }); - } - - for (name in materials) { - if (materials.hasOwnProperty(name)) { - var material = materials[name]; - var materialId = getMaterialId(name); - var values = { - ambient : defaultValue(defaultValue(getTextureId(material.ambientColorMap), material.ambientColor), [0, 0, 0, 1]), - diffuse : defaultValue(defaultValue(getTextureId(material.diffuseColorMap), material.diffuseColor), [0, 0, 0, 1]), - emission : defaultValue(defaultValue(getTextureId(material.emissionColorMap), material.emissionColor), [0, 0, 0, 1]), - specular : defaultValue(defaultValue(getTextureId(material.specularColorMap), material.specularColor), [0, 0, 0, 1]), - shininess : defaultValue(material.specularShininess, 0.0) - }; - - gltf.materials[materialId] = { - name: name, - values: values - }; + function getTextureId(imagePath) { + if (!defined(imagePath) || !defined(images[imagePath])) { + return undefined; } + return 'texture_' + getImageId(imagePath); } - for (name in images) { - if (images.hasOwnProperty(name)) { - var image = images[name]; - var imageId = getImageId(name); - var textureId = getTextureId(name); - var format; - var channels = image.channels; - switch (channels) { - case 1: - format = WebGLConstants.ALPHA; - break; - case 2: - format = WebGLConstants.LUMINANCE_ALPHA; - break; - case 3: - format = WebGLConstants.RGB; - break; - case 4: - format = WebGLConstants.RGBA; - break; + 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 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); + + var transparent; + var transparency = 1.0; + if (typeof diffuse === 'string') { + transparency = alpha; + transparent = images[material.diffuseColorMap].transparent || (transparency < 1.0); + } else { + diffuse[3] = alpha; + transparent = diffuse[3] < 1.0; + } + + var doubleSided = transparent; + + if (!hasNormals) { + // Constant technique only factors in ambient and emission sources - set emission to diffuse + emission = diffuse; + } + + var technique = hasNormals ? (hasSpecular ? 'PHONG' : 'LAMBERT') : 'CONSTANT'; + return { + extensions : { + KHR_materials_common : { + technique : technique, + values : { + ambient : ambient, + diffuse : diffuse, + emission : emission, + specular : specular, + shininess : shininess, + transparency : transparency, + transparent : transparent, + doubleSided : doubleSided + } + } } + }; + } + + if (Object.keys(images).length > 0) { + gltf.samplers[samplerId] = { + magFilter : WebGLConstants.LINEAR, + minFilter : WebGLConstants.LINEAR, + wrapS : WebGLConstants.REPEAT, + wrapT : WebGLConstants.REPEAT + }; + } + + for (var imagePath in images) { + if (images.hasOwnProperty(imagePath)) { + var image = images[imagePath]; + var imageId = getImageId(imagePath); + var textureId = getTextureId(imagePath); gltf.images[imageId] = { + name : imageId, uri : image.uri }; gltf.textures[textureId] = { - format : format, - internalFormat : format, + format : image.format, + internalFormat : image.format, sampler : samplerId, source : imageId, target : WebGLConstants.TEXTURE_2D, @@ -330,9 +142,191 @@ function createGltf(data, inputPath, modelName) { } } - if (bufferSeparate) { - var bufferPath = path.join(inputPath, modelName + '.bin'); - return fsWriteFile(bufferPath, buffer); + var vertexBuffers = []; + var vertexByteOffset = 0; + var indexBuffers = []; + var indexBuffersByteOffset = 0; + var accessorCount = 0; + + function addVertexAttribute(array, components) { + var count = array.length / components; + var buffer = array.toFloatBuffer(); + var minMax = array.getMinMax(components); + + var type = (components === 3 ? 'VEC3' : 'VEC2'); + var accessor = { + bufferView : vertexBufferViewId, + byteOffset : vertexByteOffset, + byteStride : 0, + componentType : WebGLConstants.FLOAT, + count : count, + min : minMax.min, + max : minMax.max, + type : type + }; + + vertexByteOffset += buffer.length; + vertexBuffers.push(buffer); + var accessorId = 'accessor_' + accessorCount++; + gltf.accessors[accessorId] = accessor; + return accessorId; } + + function addIndexArray(array, uint32Indices) { + var buffer = uint32Indices ? array.toUint32Buffer() : array.toUint16Buffer(); + var componentType = uint32Indices ? WebGLConstants.UNSIGNED_INT : WebGLConstants.UNSIGNED_SHORT; + var length = array.length; + var minMax = array.getMinMax(1); + var accessor = { + bufferView : indexBufferViewId, + byteOffset : indexBuffersByteOffset, + byteStride : 0, + componentType : componentType, + count : length, + min : minMax.min, + max : minMax.max, + type : 'SCALAR' + }; + + indexBuffersByteOffset += buffer.length; + indexBuffers.push(buffer); + + var accessorId = 'accessor_' + accessorCount++; + gltf.accessors[accessorId] = accessor; + return accessorId; + } + + function requiresUint32Indices(nodes) { + var nodesLength = nodes.length; + for (var i = 0; i < nodesLength; ++i) { + var meshes = nodes[i].meshes; + var meshesLength = meshes.length; + for (var j = 0; j < meshesLength; ++j) { + // Reserve the 65535 index for primitive restart + var vertexCount = meshes[j].positions.length / 3; + if (vertexCount > 65534) { + return true; + } + } + } + return false; + } + + var uint32Indices = requiresUint32Indices(nodes); + var gltfSceneNodes = gltf.scenes[sceneId].nodes; + var nodesLength = nodes.length; + for (var i = 0; i < nodesLength; ++i) { + // Add node + var node = nodes[i]; + var nodeId = node.name; + gltfSceneNodes.push(nodeId); + var gltfNodeMeshes = []; + gltf.nodes[nodeId] = { + name : nodeId, + meshes : gltfNodeMeshes + }; + + // Add meshes to node + var meshes = node.meshes; + var meshesLength = meshes.length; + for (var j = 0; j < meshesLength; ++j) { + var mesh = meshes[j]; + var meshId = mesh.name; + gltfNodeMeshes.push(meshId); + + var hasPositions = mesh.positions.length > 0; + var hasNormals = mesh.normals.length > 0; + var hasUVs = mesh.uvs.length > 0; + + var attributes = {}; + if (hasPositions) { + attributes.POSITION = addVertexAttribute(mesh.positions, 3); + } + if (hasNormals) { + attributes.NORMAL = addVertexAttribute(mesh.normals, 3); + } + if (hasUVs) { + attributes.TEXCOORD_0 = addVertexAttribute(mesh.uvs, 2); + } + + // Unload resources + mesh.positions = undefined; + mesh.normals = undefined; + mesh.uvs = undefined; + + var gltfMeshPrimitives = []; + gltf.meshes[meshId] = { + name : meshId, + primitives : gltfMeshPrimitives + }; + + // Add primitives to mesh + var primitives = mesh.primitives; + var primitivesLength = primitives.length; + for (var k = 0; k < primitivesLength; ++k) { + var primitive = primitives[k]; + var indexAccessorId = addIndexArray(primitive.indices, uint32Indices); + primitive.indices = undefined; // Unload resources + var materialId = primitive.material; + + if (!defined(materialId)) { + // Create a default material if the primitive does not specify one + materialId = 'default'; + } + + var material = defaultValue(materials[materialId], {}); + var gltfMaterial = gltf.materials[materialId]; + if (defined(gltfMaterial)) { + // Check if this material has already been added but with incompatible shading + var normalShading = (gltfMaterial.extensions.KHR_materials_common.technique !== 'CONSTANT'); + if (hasNormals !== normalShading) { + materialId += (hasNormals ? '_shaded' : '_constant'); + gltfMaterial = gltf.materials[materialId]; + } + } + + if (!defined(gltfMaterial)) { + gltf.materials[materialId] = createMaterial(material, hasNormals); + } + + gltfMeshPrimitives.push({ + attributes : attributes, + indices : indexAccessorId, + material : materialId, + mode : WebGLConstants.TRIANGLES + }); + } + } + } + + var vertexBuffer = Buffer.concat(vertexBuffers); + var indexBuffer = Buffer.concat(indexBuffers); + var buffer = Buffer.concat([vertexBuffer, indexBuffer]); + + // Buffers larger than ~192MB cannot be base64 encoded due to a NodeJS limitation. Instead the buffer will be saved to a .bin file. Source: https://github.com/nodejs/node/issues/4266 + var bufferUri; + if (buffer.length <= 201326580) { + bufferUri = 'data:application/octet-stream;base64,' + buffer.toString('base64'); + } + + gltf.buffers[bufferId] = { + byteLength : buffer.byteLength, + uri : bufferUri + }; + + gltf.bufferViews[vertexBufferViewId] = { + buffer : bufferId, + byteLength : vertexBuffer.length, + byteOffset : 0, + target : WebGLConstants.ARRAY_BUFFER + }; + + gltf.bufferViews[indexBufferViewId] = { + buffer : bufferId, + byteLength : indexBuffer.length, + byteOffset : vertexBuffer.length, + target : WebGLConstants.ELEMENT_ARRAY_BUFFER + }; + return gltf; } diff --git a/lib/image.js b/lib/image.js index 0be148b..9d2ad74 100644 --- a/lib/image.js +++ b/lib/image.js @@ -1,12 +1,55 @@ -"use strict"; -var Promise = require('bluebird'); +'use strict'; +var Cesium = require('cesium'); var fs = require('fs-extra'); var path = require('path'); +var Promise = require('bluebird'); var fsReadFile = Promise.promisify(fs.readFile); +var WebGLConstants = Cesium.WebGLConstants; + module.exports = loadImage; +/** + * Load an image file and get information about it. + * + * @param {String} imagePath Path to the image file. + * @returns {Promise} A promise resolving to the image information, or undefined if the file doesn't exist. + * + * @private + */ +function loadImage(imagePath) { + return fsReadFile(imagePath) + .then(function(data) { + var extension = path.extname(imagePath); + var uriType = getUriType(extension); + var uri = uriType + ';base64,' + data.toString('base64'); + + var info = { + transparent : false, + channels : 3, + data : data, + uri : uri, + format : getFormat(3) + }; + + if (extension === '.png') { + // Color type is encoded in the 25th bit of the png + var colorType = data[25]; + var channels = getChannels(colorType); + info.channels = channels; + info.transparent = (channels === 4); + info.format = getFormat(channels); + } + + return info; + }) + .catch(function() { + console.log('Could not read image file at ' + imagePath + '. Material will ignore this image.'); + return undefined; + }); +} + function getChannels(colorType) { switch (colorType) { case 0: // greyscale @@ -24,40 +67,27 @@ function getChannels(colorType) { function getUriType(extension) { switch (extension) { - case 'png': + case '.png': return 'data:image/png'; - case 'jpg': + case '.jpg': + case '.jpeg': return 'data:image/jpeg'; - case 'jpeg': - return 'data:image/jpeg'; - case 'gif': + case '.gif': return 'data:image/gif'; default: - return 'data:image/' + extension; + return 'data:image/' + extension.slice(1); } } -function loadImage(imagePath) { - return fsReadFile(imagePath) - .then(function(data) { - var extension = path.extname(imagePath).slice(1); - var uriType = getUriType(extension); - var uri = uriType + ';base64,' + data.toString('base64'); - - var info = { - transparent: false, - channels: 3, - data: data, - uri: uri - }; - - if (path.extname(imagePath) === 'png') { - // Color type is encoded in the 25th bit of the png - var colorType = data[25]; - var channels = getChannels(colorType); - info.channels = channels; - info.transparent = (channels === 4); - } - return info; - }); +function getFormat(channels) { + switch (channels) { + case 1: + return WebGLConstants.ALPHA; + case 2: + return WebGLConstants.LUMINANCE_ALPHA; + case 3: + return WebGLConstants.RGB; + case 4: + return WebGLConstants.RGBA; + } } diff --git a/lib/mtl.js b/lib/mtl.js index 941be7f..50a93fe 100644 --- a/lib/mtl.js +++ b/lib/mtl.js @@ -1,114 +1,105 @@ -"use strict"; -var Promise = require('bluebird'); -var fs = require('fs-extra'); -var defined = require('cesium').defined; +'use strict'; +var path = require('path'); +var readLines = require('./readLines'); -var fsReadFile = Promise.promisify(fs.readFile); +module.exports = loadMtl; -module.exports = { - getDefault : getDefault, - parse : parse -}; - -function createMaterial() { - return { - ambientColor : undefined, // Ka - emissionColor : undefined, // Ke - diffuseColor : undefined, // Kd - specularColor : undefined, // Ks - specularShininess : undefined, // Ns - alpha : undefined, // d / Tr - ambientColorMap : undefined, // map_Ka - emissionColorMap : undefined, // map_Ke - diffuseColorMap : undefined, // map_Kd - specularColorMap : undefined, // map_Ks - specularShininessMap : undefined, // map_Ns - normalMap : undefined, // map_Bump - alphaMap : undefined // map_d - }; +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 } -function getDefault() { - var material = createMaterial(); - material.diffuseColor = [0.5, 0.5, 0.5, 1.0]; - return material; -} +/** + * 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. + * + * @private + */ +function loadMtl(mtlPath) { + var material; + var values; + var value; + var materials = {}; -function parse(mtlPath) { - return fsReadFile(mtlPath, 'utf8') - .then(function (contents) { - var materials = {}; - var material; - var values; - var value; - var lines = contents.split('\n'); - var length = lines.length; - for (var i = 0; i < length; ++i) { - var line = lines[i].trim(); - if (/^newmtl /i.test(line)) { - var name = line.substring(7).trim(); - material = createMaterial(); - materials[name] = material; - } else if (/^Ka /i.test(line)) { - values = line.substring(3).trim().split(' '); - material.ambientColor = [ - parseFloat(values[0]), - parseFloat(values[1]), - parseFloat(values[2]), - 1.0 - ]; - } else if (/^Ke /i.test(line)) { - values = line.substring(3).trim().split(' '); - material.emissionColor = [ - parseFloat(values[0]), - parseFloat(values[1]), - parseFloat(values[2]), - 1.0 - ]; - } else if (/^Kd /i.test(line)) { - values = line.substring(3).trim().split(' '); - material.diffuseColor = [ - parseFloat(values[0]), - parseFloat(values[1]), - parseFloat(values[2]), - 1.0 - ]; - } else if (/^Ks /i.test(line)) { - values = line.substring(3).trim().split(' '); - material.specularColor = [ - parseFloat(values[0]), - parseFloat(values[1]), - parseFloat(values[2]), - 1.0 - ]; - } else if (/^Ns /i.test(line)) { - value = line.substring(3).trim(); - material.specularShininess = parseFloat(value); - } else if (/^d /i.test(line)) { - value = line.substring(2).trim(); - material.alpha = parseFloat(value); - } else if (/^Tr /i.test(line)) { - value = line.substring(3).trim(); - material.alpha = parseFloat(value); - } else if (/^map_Ka /i.test(line)) { - material.ambientColorMap = line.substring(7).trim(); - } else if (/^map_Ke /i.test(line)) { - material.emissionColorMap = line.substring(7).trim(); - } else if (/^map_Kd /i.test(line)) { - material.diffuseColorMap = line.substring(7).trim(); - } else if (/^map_Ks /i.test(line)) { - material.specularColorMap = line.substring(7).trim(); - } else if (/^map_Ns /i.test(line)) { - material.specularShininessMap = line.substring(7).trim(); - } else if (/^map_Bump /i.test(line)) { - material.normalMap = line.substring(9).trim(); - } else if (/^map_d /i.test(line)) { - material.alphaMap = line.substring(6).trim(); - } - } - if (defined(material.alpha)) { - material.diffuseColor[3] = material.alpha; - } + function parseLine(line) { + line = line.trim(); + if (/^newmtl /i.test(line)) { + var name = line.substring(7).trim(); + material = new Material(); + materials[name] = material; + } else if (/^Ka /i.test(line)) { + values = line.substring(3).trim().split(' '); + material.ambientColor = [ + parseFloat(values[0]), + parseFloat(values[1]), + parseFloat(values[2]), + 1.0 + ]; + } else if (/^Ke /i.test(line)) { + values = line.substring(3).trim().split(' '); + material.emissionColor = [ + parseFloat(values[0]), + parseFloat(values[1]), + parseFloat(values[2]), + 1.0 + ]; + } else if (/^Kd /i.test(line)) { + values = line.substring(3).trim().split(' '); + material.diffuseColor = [ + parseFloat(values[0]), + parseFloat(values[1]), + parseFloat(values[2]), + 1.0 + ]; + } else if (/^Ks /i.test(line)) { + values = line.substring(3).trim().split(' '); + material.specularColor = [ + parseFloat(values[0]), + parseFloat(values[1]), + parseFloat(values[2]), + 1.0 + ]; + } else if (/^Ns /i.test(line)) { + value = line.substring(3).trim(); + material.specularShininess = parseFloat(value); + } else if (/^d /i.test(line)) { + value = line.substring(2).trim(); + material.alpha = parseFloat(value); + } else if (/^Tr /i.test(line)) { + value = line.substring(3).trim(); + material.alpha = parseFloat(value); + } else if (/^map_Ka /i.test(line)) { + material.ambientColorMap = getAbsolutePath(line.substring(7).trim(), mtlPath); + } else if (/^map_Ke /i.test(line)) { + material.emissionColorMap = getAbsolutePath(line.substring(7).trim(), mtlPath); + } else if (/^map_Kd /i.test(line)) { + material.diffuseColorMap = getAbsolutePath(line.substring(7).trim(), mtlPath); + } else if (/^map_Ks /i.test(line)) { + material.specularColorMap = getAbsolutePath(line.substring(7).trim(), mtlPath); + } else if (/^map_Ns /i.test(line)) { + material.specularShininessMap = getAbsolutePath(line.substring(7).trim(), mtlPath); + } else if (/^map_Bump /i.test(line)) { + material.normalMap = getAbsolutePath(line.substring(9).trim(), mtlPath); + } else if (/^map_d /i.test(line)) { + material.alphaMap = getAbsolutePath(line.substring(6).trim(), mtlPath); + } + } + + return readLines(mtlPath, parseLine) + .then(function() { return materials; }) .catch(function() { @@ -116,3 +107,10 @@ function parse(mtlPath) { return {}; }); } + +function getAbsolutePath(imagePath, mtlPath) { + if (!path.isAbsolute(imagePath)) { + imagePath = path.join(path.dirname(mtlPath), imagePath); + } + return imagePath; +} diff --git a/lib/obj.js b/lib/obj.js index b60e569..abe024d 100644 --- a/lib/obj.js +++ b/lib/obj.js @@ -1,365 +1,447 @@ -"use strict"; - +'use strict'; var Cesium = require('cesium'); -var Promise = require('bluebird'); -var byline = require('byline'); -var fs = require('fs-extra'); var path = require('path'); +var Promise = require('bluebird'); +var ArrayStorage = require('./ArrayStorage'); var loadImage = require('./image'); -var Material = require('./mtl'); +var loadMtl = require('./mtl'); +var readLines = require('./readLines'); -var Cartesian3 = Cesium.Cartesian3; +var combine = Cesium.combine; +var ComponentDatatype = Cesium.ComponentDatatype; +var defaultValue = Cesium.defaultValue; var defined = Cesium.defined; +var RuntimeError = Cesium.RuntimeError; -module.exports = parseObj; +module.exports = loadObj; -// OBJ regex patterns are from ThreeJS (https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/OBJLoader.js) +// Object name (o) -> node +// Group name (g) -> mesh +// Material name (usemtl) -> primitive -function parseObj(objFile, inputPath) { - return getObjInfo(objFile, inputPath) - .then(function(result) { - var info = result.info; - var materials = result.materials; - var images = result.images; - return processObj(objFile, info, materials, images); +function Node() { + this.name = undefined; + this.meshes = []; +} + +function Mesh() { + this.name = undefined; + this.primitives = []; + this.positions = new ArrayStorage(ComponentDatatype.FLOAT); + this.normals = new ArrayStorage(ComponentDatatype.FLOAT); + this.uvs = new ArrayStorage(ComponentDatatype.FLOAT); +} + +function Primitive() { + this.material = undefined; + this.indices = new ArrayStorage(ComponentDatatype.UNSIGNED_INT); +} + +// OBJ regex patterns are modified from ThreeJS (https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/OBJLoader.js) +var vertexPattern = /v( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // v float float float +var normalPattern = /vn( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // vn float float float +var uvPattern = /vt( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; // vt float float +var facePattern1 = /f( +-?\d+)\/?( +-?\d+)\/?( +-?\d+)\/?( +-?\d+)?\/?/; // f vertex vertex vertex ... +var facePattern2 = /f( +(-?\d+)\/(-?\d+)\/?)( +(-?\d+)\/(-?\d+)\/?)( +(-?\d+)\/(-?\d+)\/?)( +(-?\d+)\/(-?\d+)\/?)?/; // f vertex/uv vertex/uv vertex/uv ... +var facePattern3 = /f( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))?/; // f vertex/uv/normal vertex/uv/normal vertex/uv/normal ... +var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/; // f vertex//normal vertex//normal vertex//normal ... + +/** + * Parse an obj file. + * + * @param {String} objPath Path to the obj file. + * @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) { + // Global store of vertex attributes listed in the obj file + var positions = new ArrayStorage(ComponentDatatype.FLOAT); + var normals = new ArrayStorage(ComponentDatatype.FLOAT); + var uvs = new ArrayStorage(ComponentDatatype.FLOAT); + + // The current node, mesh, and primitive + var node; + var mesh; + var primitive; + + // All nodes seen in the obj + var nodes = []; + + // Used to build the indices. The vertex cache is unique to each mesh. + var vertexCache = {}; + var vertexCacheLimit = 1000000; + var vertexCacheCount = 0; + var vertexCount = 0; + + // All mtl paths seen in the obj + var mtlPaths = []; + + function getName(name) { + return (name === '' ? undefined : name); + } + + function addNode(name) { + node = new Node(); + node.name = getName(name); + nodes.push(node); + addMesh(); + } + + function addMesh(name) { + mesh = new Mesh(); + mesh.name = getName(name); + node.meshes.push(mesh); + addPrimitive(); + + // Clear the vertex cache for each new mesh + vertexCache = {}; + vertexCacheCount = 0; + vertexCount = 0; + } + + function addPrimitive() { + primitive = new Primitive(); + mesh.primitives.push(primitive); + } + + function useMaterial(name) { + // Look to see if this material has already been used by a primitive in the mesh + var material = getName(name); + var primitives = mesh.primitives; + var primitivesLength = primitives.length; + for (var i = 0; i < primitivesLength; ++i) { + primitive = primitives[i]; // Sets the active primitive in case of returning early + if (primitive.material === material) { + return; + } + } + // Add a new primitive with this material + addPrimitive(); + primitive.material = getName(name); + } + + function getOffset(a, attributeData, components) { + var i = parseInt(a); + if (i < 0) { + // Negative vertex indexes reference the vertices immediately above it + return (attributeData.length / components + i) * components; + } + return (i - 1) * components; + } + + function createVertex(p, u, n) { + // Positions + if (defined(p)) { + var pi = getOffset(p, positions, 3); + var px = positions.get(pi + 0); + var py = positions.get(pi + 1); + var pz = positions.get(pi + 2); + mesh.positions.push(px); + mesh.positions.push(py); + mesh.positions.push(pz); + } + + // Normals + if (defined(n)) { + var ni = getOffset(n, normals, 3); + var nx = normals.get(ni + 0); + var ny = normals.get(ni + 1); + var nz = normals.get(ni + 2); + mesh.normals.push(nx); + mesh.normals.push(ny); + mesh.normals.push(nz); + } + + // UVs + if (defined(u)) { + var ui = getOffset(u, uvs, 2); + var ux = uvs.get(ui + 0); + var uy = uvs.get(ui + 1); + mesh.uvs.push(ux); + mesh.uvs.push(uy); + } + } + + function addVertex(v, p, u, n) { + var index = vertexCache[v]; + if (!defined(index)) { + index = vertexCount++; + vertexCache[v] = index; + createVertex(p, u, n); + + // Prevent the vertex cache from growing too large. As a result of clearing the cache there + // may be some duplicate vertices. + vertexCacheCount++; + if (vertexCacheCount > vertexCacheLimit) { + vertexCacheCount = 0; + vertexCache = {}; + } + } + return index; + } + + function addFace(v1, p1, u1, n1, v2, p2, u2, n2, v3, p3, u3, n3, v4, p4, u4, n4) { + var index1 = addVertex(v1, p1, u1, n1); + var index2 = addVertex(v2, p2, u2, n2); + var index3 = addVertex(v3, p3, u3, n3); + + primitive.indices.push(index1); + primitive.indices.push(index2); + primitive.indices.push(index3); + + // Triangulate if the face is a quad + if (defined(v4)) { + var index4 = addVertex(v4, p4, u4, n4); + primitive.indices.push(index1); + primitive.indices.push(index3); + primitive.indices.push(index4); + } + } + + function parseLine(line) { + line = line.trim(); + var result; + + if ((line.length === 0) || (line.charAt(0) === '#')) { + // Don't process empty lines or comments + } else if (/^o\s/i.test(line)) { + var objectName = line.substring(2).trim(); + addNode(objectName); + } else if (/^g\s/i.test(line)) { + var groupName = line.substring(2).trim(); + addMesh(groupName); + } else if (/^usemtl\s/i.test(line)) { + var materialName = line.substring(7).trim(); + useMaterial(materialName); + } else if (/^mtllib/i.test(line)) { + var paths = line.substring(7).trim().split(' '); + mtlPaths = mtlPaths.concat(paths); + } else if ((result = vertexPattern.exec(line)) !== null) { + positions.push(parseFloat(result[1])); + positions.push(parseFloat(result[2])); + positions.push(parseFloat(result[3])); + } else if ((result = normalPattern.exec(line) ) !== null) { + normals.push(parseFloat(result[1])); + normals.push(parseFloat(result[2])); + normals.push(parseFloat(result[3])); + } else if ((result = uvPattern.exec(line)) !== null) { + uvs.push(parseFloat(result[1])); + uvs.push(1.0 - parseFloat(result[2])); // Flip y so 0.0 is the bottom of the image + } else if ((result = facePattern1.exec(line)) !== null) { + addFace( + result[1], result[1], undefined, undefined, + result[2], result[2], undefined, undefined, + result[3], result[3], undefined, undefined, + result[4], result[4], undefined, undefined + ); + } else if ((result = facePattern2.exec(line)) !== null) { + addFace( + result[1], result[2], result[3], undefined, + result[4], result[5], result[6], undefined, + result[7], result[8], result[9], undefined, + result[10], result[11], result[12], undefined + ); + } else if ((result = facePattern3.exec(line)) !== null) { + addFace( + result[1], result[2], result[3], result[4], + result[5], result[6], result[7], result[8], + result[9], result[10], result[11], result[12], + result[13], result[14], result[15], result[16] + ); + } else if ((result = facePattern4.exec(line)) !== null) { + addFace( + result[1], result[2], undefined, result[3], + result[4], result[5], undefined, result[6], + result[7], result[8], undefined, result[9], + result[10], result[11], undefined, result[12] + ); + } + } + + // Create a default node in case there are no o/g/usemtl lines in the obj + addNode(); + + // Parse the obj file + return readLines(objPath, parseLine) + .then(function() { + // Unload resources + positions = undefined; + normals = undefined; + uvs = undefined; + + // Load materials and images + return finishLoading(nodes, mtlPaths, objPath); }); } -function processObj(objFile, info, materials, images) { - return new Promise(function(resolve) { - // A vertex is specified by indexes into each of the attribute arrays, - // but these indexes may be different. This maps the separate indexes to a single index. - var vertexCache = {}; - var vertexCount = 0; - - var vertexArray = []; - - var positions = []; - var normals = []; - var uvs = []; - - var positionMin = [Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE]; - var positionMax = [-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE]; - - var hasNormals = info.hasNormals; - var hasUVs = info.hasUVs; - - var materialGroups = {}; // Map material to index array - var currentIndexArray; - - // Switch to the material-specific index array, or create it if it doesn't exist - function useMaterial(material) { - if (!defined(materials[material])) { - useDefaultMaterial(); - } else { - currentIndexArray = materialGroups[material]; - if (!defined(currentIndexArray)) { - currentIndexArray = []; - materialGroups[material] = currentIndexArray; - } - } - } - - function useDefaultMaterial() { - var defaultMaterial = 'czmDefaultMat'; - if (!defined(materials[defaultMaterial])) { - materials[defaultMaterial] = Material.getDefault(); - } - useMaterial(defaultMaterial); - } - - var materialsLength = Object.keys(materials).length; - if (materialsLength === 0) { - useDefaultMaterial(); - } - - function getOffset(a, data, components) { - var i = parseInt(a); - if (i < 0) { - // Negative vertex indexes reference the vertices immediately above it - return (data.length / components + i) * components; - } - return (i - 1) * components; - } - - function createVertex(p, u, n) { - // Positions - var pi = getOffset(p, positions, 3); - var px = positions[pi + 0]; - var py = positions[pi + 1]; - var pz = positions[pi + 2]; - - positionMin[0] = Math.min(px, positionMin[0]); - positionMin[1] = Math.min(py, positionMin[1]); - positionMin[2] = Math.min(pz, positionMin[2]); - positionMax[0] = Math.max(px, positionMax[0]); - positionMax[1] = Math.max(py, positionMax[1]); - positionMax[2] = Math.max(pz, positionMax[2]); - vertexArray.push(px, py, pz); - - // Normals - if (hasNormals) { - var ni = getOffset(n, normals, 3); - var nx = normals[ni + 0]; - var ny = normals[ni + 1]; - var nz = normals[ni + 2]; - vertexArray.push(nx, ny, nz); - } - - // UVs - if (hasUVs) { - if (defined(u)) { - var ui = getOffset(u, uvs, 2); - var ux = uvs[ui + 0]; - var uy = uvs[ui + 1]; - // Flip y so 0.0 is the bottom of the image - uy = 1.0 - uy; - vertexArray.push(ux, uy); - } else { - // Some objects in the model may not have uvs, fill with 0's for consistency - vertexArray.push(0.0, 0.0); - } - } - } - - function addVertex(v, p, u, n) { - var index = vertexCache[v]; - if (!defined(index)) { - index = vertexCount++; - vertexCache[v] = index; - createVertex(p, u, n); - } - - return index; - } - - function addFace(v1, p1, u1, n1, v2, p2, u2, n2, v3, p3, u3, n3, v4, p4, u4, n4) { - var index1 = addVertex(v1, p1, u1, n1); - var index2 = addVertex(v2, p2, u2, n2); - var index3 = addVertex(v3, p3, u3, n3); - - currentIndexArray.push(index1); - currentIndexArray.push(index2); - currentIndexArray.push(index3); - - // Triangulate if the face is a quad - if (defined(v4)) { - var index4 = addVertex(v4, p4, u4, n4); - currentIndexArray.push(index1); - currentIndexArray.push(index3); - currentIndexArray.push(index4); - } - } - - // v float float float - var vertexPattern = /v( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; - - // vn float float float - var normalPattern = /vn( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; - - // vt float float - var uvPattern = /vt( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/; - - // f vertex vertex vertex ... - var facePattern1 = /f( +-?\d+)\/?( +-?\d+)\/?( +-?\d+)\/?( +-?\d+)?\/?/; - - // f vertex/uv vertex/uv vertex/uv ... - var facePattern2 = /f( +(-?\d+)\/(-?\d+)\/?)( +(-?\d+)\/(-?\d+)\/?)( +(-?\d+)\/(-?\d+)\/?)( +(-?\d+)\/(-?\d+)\/?)?/; - - // f vertex/uv/normal vertex/uv/normal vertex/uv/normal ... - var facePattern3 = /f( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))?/; - - // f vertex//normal vertex//normal vertex//normal ... - var facePattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/; - - var stream = byline(fs.createReadStream(objFile, {encoding: 'utf8'})); - stream.on('data', function (line) { - line = line.trim(); - var result; - if ((line.length === 0) || (line.charAt(0) === '#')) { - // Don't process empty lines or comments - } else if ((result = vertexPattern.exec(line)) !== null) { - positions.push( - parseFloat(result[1]), - parseFloat(result[2]), - parseFloat(result[3]) - ); - } else if ((result = normalPattern.exec(line) ) !== null) { - var nx = parseFloat(result[1]); - var ny = parseFloat(result[2]); - var nz = parseFloat(result[3]); - var normal = Cartesian3.normalize(new Cartesian3(nx, ny, nz), new Cartesian3()); - normals.push(normal.x, normal.y, normal.z); - } else if ((result = uvPattern.exec(line)) !== null) { - uvs.push( - parseFloat(result[1]), - parseFloat(result[2]) - ); - } else if ((result = facePattern1.exec(line)) !== null) { - addFace( - result[1], result[1], undefined, undefined, - result[2], result[2], undefined, undefined, - result[3], result[3], undefined, undefined, - result[4], result[4], undefined, undefined - ); - } else if ((result = facePattern2.exec(line)) !== null) { - addFace( - result[1], result[2], result[3], undefined, - result[4], result[5], result[6], undefined, - result[7], result[8], result[9], undefined, - result[10], result[11], result[12], undefined - ); - } else if ((result = facePattern3.exec(line)) !== null) { - addFace( - result[1], result[2], result[3], result[4], - result[5], result[6], result[7], result[8], - result[9], result[10], result[11], result[12], - result[13], result[14], result[15], result[16] - ); - } else if ((result = facePattern4.exec(line)) !== null) { - addFace( - result[1], result[2], undefined, result[3], - result[4], result[5], undefined, result[6], - result[7], result[8], undefined, result[9], - result[10], result[11], undefined, result[12] - ); - } else if (/^usemtl /.test(line)) { - var materialName = line.substring(7).trim(); - useMaterial(materialName); - } +function finishLoading(nodes, mtlPaths, objPath) { + nodes = cleanNodes(nodes); + if (nodes.length === 0) { + throw new RuntimeError(objPath + ' does not have any geometry data'); + } + return loadMaterials(mtlPaths, objPath) + .then(function(materials) { + var imagePaths = getImagePaths(materials); + return loadImages(imagePaths, objPath) + .then(function(images) { + return { + nodes : nodes, + materials : materials, + images : images + }; + }); }); +} - stream.on('end', function () { - resolve({ - vertexCount: vertexCount, - vertexArray: vertexArray, - positionMin: positionMin, - positionMax: positionMax, - hasUVs: hasUVs, - hasNormals: hasNormals, - materialGroups: materialGroups, - materials: materials, - images: images +function getAbsolutePath(mtlPath, objPath) { + if (!path.isAbsolute(mtlPath)) { + mtlPath = path.join(path.dirname(objPath), mtlPath); + } + return mtlPath; +} + +function loadMaterials(mtlPaths, objPath) { + var materials = {}; + return Promise.map(mtlPaths, function(mtlPath) { + mtlPath = getAbsolutePath(mtlPath, objPath); + return loadMtl(mtlPath) + .then(function(materialsInMtl) { + materials = combine(materials, materialsInMtl); }); - }); + }).then(function() { + return materials; }); } -function getImages(inputPath, materials) { - // Collect all the image files from the materials - var images = []; +function loadImages(imagePaths) { + var images = {}; + return Promise.map(imagePaths, function(imagePath) { + return loadImage(imagePath) + .then(function(image) { + if (defined(image)) { + images[imagePath] = image; + } + }); + }).then(function() { + return images; + }); +} + +function getImagePaths(materials) { + var imagePaths = []; for (var name in materials) { if (materials.hasOwnProperty(name)) { var material = materials[name]; - if (defined(material.ambientColorMap) && (images.indexOf(material.ambientColorMap) === -1)) { - images.push(material.ambientColorMap); + if (defined(material.ambientColorMap) && imagePaths.indexOf(material.ambientColorMap) === -1) { + imagePaths.push(material.ambientColorMap); } - if (defined(material.diffuseColorMap) && (images.indexOf(material.diffuseColorMap) === -1)) { - images.push(material.diffuseColorMap); + if (defined(material.diffuseColorMap) && imagePaths.indexOf(material.diffuseColorMap) === -1) { + imagePaths.push(material.diffuseColorMap); } - if (defined(material.emissionColorMap) && (images.indexOf(material.emissionColorMap) === -1)) { - images.push(material.emissionColorMap); + if (defined(material.emissionColorMap) && imagePaths.indexOf(material.emissionColorMap) === -1) { + imagePaths.push(material.emissionColorMap); } - if (defined(material.specularColorMap) && (images.indexOf(material.specularColorMap) === -1)) { - images.push(material.specularColorMap); + if (defined(material.specularColorMap) && imagePaths.indexOf(material.specularColorMap) === -1) { + imagePaths.push(material.specularColorMap); } } } + return imagePaths; +} - // Load the image files - var promises = []; - var imagesInfo = {}; - var imagesLength = images.length; - for (var i = 0; i < imagesLength; i++) { - var imagePath = images[i]; - if (!path.isAbsolute(imagePath)) { - imagePath = path.join(inputPath, imagePath); +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); } - promises.push(loadImage(imagePath)); } - return Promise.all(promises) - .then(function(imageInfoArray) { - var imageInfoArrayLength = imageInfoArray.length; - for (var j = 0; j < imageInfoArrayLength; j++) { - var image = images[j]; - var imageInfo = imageInfoArray[j]; - imagesInfo[image] = imageInfo; - } - return imagesInfo; - }); + return final; } -function getMaterials(mtlPath, hasMaterialGroups) { - if (hasMaterialGroups && defined(mtlPath)) { - return Material.parse(mtlPath); +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 {}; + return final; } -function getObjInfo(objFile, inputPath) { - var mtlPath; - var materials; - var info; - var hasMaterialGroups = false; - var hasPositions = false; - var hasNormals = false; - var hasUVs = false; - return new Promise(function(resolve, reject) { - var stream = byline(fs.createReadStream(objFile, {encoding: 'utf8'})); - stream.on('data', function (line) { - if (!defined(mtlPath)) { - var mtllibMatches = line.match(/^mtllib.*/gm); - if (mtllibMatches !== null) { - var mtlFile = mtllibMatches[0].substring(7).trim(); - mtlPath = mtlFile; - if (!path.isAbsolute(mtlPath)) { - mtlPath = path.join(inputPath, mtlFile); - } - } - } - if (!hasMaterialGroups) { - hasMaterialGroups = /^usemtl/gm.test(line); - } - if (!hasPositions) { - hasPositions = /^v\s/gm.test(line); - } - if (!hasNormals) { - hasNormals = /^vn/gm.test(line); - } - if (!hasUVs) { - hasUVs = /^vt/gm.test(line); - } - }); - - stream.on('error', function(err) { - reject(err); - }); - - stream.on('end', function () { - if (!hasPositions) { - reject(new Error('Could not process OBJ file, no positions.')); - } - info = { - hasNormals: hasNormals, - hasUVs: hasUVs - }; - resolve(); - }); - }) - .then(function() { - return getMaterials(mtlPath, hasMaterialGroups); - }) - .then(function(returnedMaterials) { - materials = returnedMaterials; - return getImages(inputPath, materials); - }) - .then(function(images) { - return { - info : info, - materials : materials, - images : images - }; - }); +function meshesHaveNames(meshes) { + var meshesLength = meshes.length; + for (var i = 0; i < meshesLength; ++i) { + if (defined(meshes[i].name)) { + return true; + } + } + return false; +} + +function removeEmptyNodes(nodes) { + var final = []; + var nodesLength = nodes.length; + for (var i = 0; i < nodesLength; ++i) { + var node = nodes[i]; + var meshes = removeEmptyMeshes(node.meshes); + if (meshes.length === 0) { + continue; + } + node.meshes = meshes; + if (!defined(node.name) && meshesHaveNames(meshes)) { + // If the obj has groups (g) but not object groups (o) then convert meshes to nodes + var meshesLength = meshes.length; + for (var j = 0; j < meshesLength; ++j) { + var mesh = meshes[j]; + var convertedNode = new Node(); + convertedNode.name = mesh.name; + convertedNode.meshes = [mesh]; + final.push(convertedNode); + } + } else { + final.push(node); + } + } + return final; +} + +function setDefaultNames(items, defaultName, usedNames) { + var itemsLength = items.length; + for (var i = 0; i < itemsLength; ++i) { + var item = items[i]; + var name = defaultValue(item.name, defaultName); + var occurrences = usedNames[name]; + if (defined(occurrences)) { + usedNames[name]++; + name = name + '_' + occurrences; + } else { + usedNames[name] = 1; + } + item.name = name; + } +} + +function setDefaults(nodes) { + var usedNames = {}; + setDefaultNames(nodes, 'Node', usedNames); + var nodesLength = nodes.length; + for (var i = 0; i < nodesLength; ++i) { + var node = nodes[i]; + setDefaultNames(node.meshes, node.name + '-Mesh', usedNames); + } +} + +function cleanNodes(nodes) { + nodes = removeEmptyNodes(nodes); + setDefaults(nodes); + return nodes; } diff --git a/lib/readLines.js b/lib/readLines.js new file mode 100644 index 0000000..4178147 --- /dev/null +++ b/lib/readLines.js @@ -0,0 +1,27 @@ +'use strict'; +var eventStream = require('event-stream'); +var fsExtra = require('fs-extra'); +var Promise = require('bluebird'); + +module.exports = readLines; + +/** + * Read a file line-by-line. + * + * @param {String} path Path to the file. + * @param {Function} callback Function to call when reading each line. + * @returns {Promise} A promise when the reader is finished. + * + * @private + */ +function readLines(path, callback) { + return new Promise(function(resolve, reject) { + fsExtra.createReadStream(path) + .on('error', reject) + .on('end', resolve) + .pipe(eventStream.split()) + .pipe(eventStream.mapSync(function (line) { + callback(line); + })); + }); +} diff --git a/package.json b/package.json index 75a2b70..67cd831 100644 --- a/package.json +++ b/package.json @@ -6,45 +6,44 @@ "contributors": [ { "name": "Analytical Graphics, Inc., and Contributors", - "url": "https://github.com/AnalyticalGraphicsInc/OBJ2GLTF/graphs/contributors" + "url": "https://github.com/AnalyticalGraphicsInc/obj2gltf/graphs/contributors" } ], "keywords": [ "obj", "gltf" ], - "homepage": "https://github.com/AnalyticalGraphicsInc/OBJ2GLTF", + "homepage": "https://github.com/AnalyticalGraphicsInc/obj2gltf", "repository": { "type": "git", - "url": "git@github.com:AnalyticalGraphicsInc/OBJ2GLTF.git" + "url": "git@github.com:AnalyticalGraphicsInc/obj2gltf.git" }, "bugs": { - "url": "https://github.com/AnalyticalGraphicsInc/OBJ2GLTF/issues" + "url": "https://github.com/AnalyticalGraphicsInc/obj2gltf/issues" }, "main": "index.js", "engines": { "node": ">=4.0.0" }, "dependencies": { - "async": "2.1.2", - "bluebird": "3.4.6", - "byline": "5.0.0", - "cesium": "1.26.0", - "fs-extra": "0.30.0", - "gltf-pipeline": "0.1.0-alpha8", - "yargs": "6.3.0" + "bluebird": "^3.4.7", + "cesium": "^1.31.0", + "event-stream": "^3.3.4", + "fs-extra": "^2.0.0", + "gltf-pipeline": "^0.1.0-alpha11", + "yargs": "^7.0.1" }, "devDependencies": { - "gulp": "3.9.1", - "gulp-jshint": "2.0.2", - "istanbul": "0.4.5", - "jasmine": "2.5.2", - "jasmine-spec-reporter": "2.7.0", - "jshint": "2.9.4", - "jshint-stylish": "2.2.1", - "open": "0.0.5", - "requirejs": "2.3.2", - "typings": "1.4.0" + "gulp": "^3.9.1", + "gulp-jshint": "^2.0.4", + "istanbul": "^0.4.5", + "jasmine": "^2.5.3", + "jasmine-spec-reporter": "^3.2.0", + "jshint": "^2.9.4", + "jshint-stylish": "^2.2.1", + "open": "^0.0.5", + "requirejs": "^2.3.3", + "typings": "^2.1.0" }, "scripts": { "prepublish": "typings install", diff --git a/specs/data/BoxTextured/CesiumLogoFlat.png b/specs/data/BoxTextured/CesiumLogoFlat.png deleted file mode 100644 index 88bada3..0000000 Binary files a/specs/data/BoxTextured/CesiumLogoFlat.png and /dev/null differ diff --git a/specs/data/box-complex-material/alpha.png b/specs/data/box-complex-material/alpha.png new file mode 100644 index 0000000..8437c62 Binary files /dev/null and b/specs/data/box-complex-material/alpha.png differ diff --git a/specs/data/box-complex-material/ambient.gif b/specs/data/box-complex-material/ambient.gif new file mode 100644 index 0000000..1276823 Binary files /dev/null and b/specs/data/box-complex-material/ambient.gif differ diff --git a/specs/data/box-complex-material/box-complex-material.mtl b/specs/data/box-complex-material/box-complex-material.mtl new file mode 100644 index 0000000..3f69a9e --- /dev/null +++ b/specs/data/box-complex-material/box-complex-material.mtl @@ -0,0 +1,20 @@ +# Blender MTL File: 'None' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.200000 0.200000 0.200000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.100000 0.100000 0.100000 +Ni 1.000000 +d 0.900000 +Tr 0.900000 +map_Ka ambient.gif +map_Ke emission.jpg +map_Kd diffuse.png +map_Ks specular.jpeg +map_Ns shininess.png +map_Bump bump.png +map_d alpha.png +illum 2 diff --git a/specs/data/BoxTextured/BoxTextured.obj b/specs/data/box-complex-material/box-complex-material.obj similarity index 73% rename from specs/data/BoxTextured/BoxTextured.obj rename to specs/data/box-complex-material/box-complex-material.obj index 64da02d..0ca30b0 100644 --- a/specs/data/BoxTextured/BoxTextured.obj +++ b/specs/data/box-complex-material/box-complex-material.obj @@ -1,7 +1,7 @@ -# Blender v2.77 (sub 0) OBJ File: 'BoxTextured.blend' +# Blender v2.78 (sub 0) OBJ File: '' # www.blender.org -mtllib BoxTextured.mtl -o Cube_Cube.001 +mtllib box-complex-material.mtl +o Cube v -1.000000 -1.000000 1.000000 v -1.000000 1.000000 1.000000 v -1.000000 -1.000000 -1.000000 @@ -26,9 +26,9 @@ vt 0.0000 0.0000 vt 1.0000 0.0000 vt 1.0000 1.0000 vt 0.0000 1.0000 -vt 0.0000 0.0000 vt 1.0000 0.0000 vt 1.0000 1.0000 +vt 0.0000 0.0000 vt 0.0000 1.0000 vn -1.0000 0.0000 0.0000 vn 0.0000 0.0000 -1.0000 @@ -36,11 +36,11 @@ vn 1.0000 0.0000 0.0000 vn 0.0000 0.0000 1.0000 vn 0.0000 -1.0000 0.0000 vn 0.0000 1.0000 0.0000 -usemtl Textured +usemtl Material s off -f 2/1/1 4/2/1 3/3/1 1/4/1 -f 4/5/2 8/6/2 7/7/2 3/8/2 -f 8/9/3 6/10/3 5/11/3 7/12/3 -f 6/13/4 2/14/4 1/15/4 5/16/4 -f 1/17/5 3/18/5 7/7/5 5/16/5 -f 6/13/6 8/6/6 4/19/6 2/20/6 +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 diff --git a/specs/data/box-complex-material/bump.png b/specs/data/box-complex-material/bump.png new file mode 100644 index 0000000..fcc5ba8 Binary files /dev/null and b/specs/data/box-complex-material/bump.png differ diff --git a/specs/data/box-complex-material/diffuse.png b/specs/data/box-complex-material/diffuse.png new file mode 100644 index 0000000..8704966 Binary files /dev/null and b/specs/data/box-complex-material/diffuse.png differ diff --git a/specs/data/box-complex-material/emission.jpg b/specs/data/box-complex-material/emission.jpg new file mode 100644 index 0000000..e5c85a9 Binary files /dev/null and b/specs/data/box-complex-material/emission.jpg differ diff --git a/specs/data/box-complex-material/shininess.png b/specs/data/box-complex-material/shininess.png new file mode 100644 index 0000000..bfcf1a2 Binary files /dev/null and b/specs/data/box-complex-material/shininess.png differ diff --git a/specs/data/box-complex-material/specular.jpeg b/specs/data/box-complex-material/specular.jpeg new file mode 100644 index 0000000..661bf98 Binary files /dev/null and b/specs/data/box-complex-material/specular.jpeg differ diff --git a/specs/data/box-groups/box-groups.mtl b/specs/data/box-groups/box-groups.mtl new file mode 100644 index 0000000..2f9b11a --- /dev/null +++ b/specs/data/box-groups/box-groups.mtl @@ -0,0 +1,32 @@ +# Blender MTL File: 'box-objects.blend' +# Material Count: 3 + +newmtl Blue +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.000000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Green +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.640000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Red +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.000000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-groups/box-groups.obj b/specs/data/box-groups/box-groups.obj new file mode 100644 index 0000000..5ac69f0 --- /dev/null +++ b/specs/data/box-groups/box-groups.obj @@ -0,0 +1,132 @@ +# Blender v2.78 (sub 0) OBJ File: 'box-objects.blend' +# www.blender.org +mtllib box-groups.mtl +g CubeBlue +v -1.000000 -1.000000 -4.000000 +v -1.000000 1.000000 -4.000000 +v -1.000000 -1.000000 -6.000000 +v -1.000000 1.000000 -6.000000 +v 1.000000 -1.000000 -4.000000 +v 1.000000 1.000000 -4.000000 +v 1.000000 -1.000000 -6.000000 +v 1.000000 1.000000 -6.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Blue +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 +g CubeGreen +v 4.000000 -1.000000 1.000000 +v 4.000000 1.000000 1.000000 +v 4.000000 -1.000000 -1.000000 +v 4.000000 1.000000 -1.000000 +v 6.000000 -1.000000 1.000000 +v 6.000000 1.000000 1.000000 +v 6.000000 -1.000000 -1.000000 +v 6.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Green +s off +f 9/21/7 10/22/7 12/23/7 11/24/7 +f 11/25/8 12/26/8 16/27/8 15/28/8 +f 15/29/9 16/30/9 14/31/9 13/32/9 +f 13/33/10 14/34/10 10/35/10 9/36/10 +f 11/25/11 15/37/11 13/38/11 9/36/11 +f 16/39/12 12/26/12 10/35/12 14/40/12 +g CubeRed +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Red +s off +f 17/41/13 18/42/13 20/43/13 19/44/13 +f 19/45/14 20/46/14 24/47/14 23/48/14 +f 23/49/15 24/50/15 22/51/15 21/52/15 +f 21/53/16 22/54/16 18/55/16 17/56/16 +f 19/45/17 23/57/17 21/58/17 17/56/17 +f 24/59/18 20/46/18 18/55/18 22/60/18 diff --git a/specs/data/box-missing-mtllib/box-missing-mtllib.obj b/specs/data/box-missing-mtllib/box-missing-mtllib.obj new file mode 100644 index 0000000..5246261 --- /dev/null +++ b/specs/data/box-missing-mtllib/box-missing-mtllib.obj @@ -0,0 +1,46 @@ +# Blender v2.78 (sub 0) OBJ File: '' +# www.blender.org +mtllib box.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Material +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 diff --git a/specs/data/BoxTextured/BoxTextured.mtl b/specs/data/box-missing-texture/box-missing-texture.mtl similarity index 57% rename from specs/data/BoxTextured/BoxTextured.mtl rename to specs/data/box-missing-texture/box-missing-texture.mtl index eeff817..c5c879f 100644 --- a/specs/data/BoxTextured/BoxTextured.mtl +++ b/specs/data/box-missing-texture/box-missing-texture.mtl @@ -1,13 +1,13 @@ -# Blender MTL File: 'BoxTextured.blend' +# Blender MTL File: 'box.blend' # Material Count: 1 -newmtl Textured +newmtl Material Ns 96.078431 -Ka 1.000000 1.000000 1.000000 +Ka 0.000000 0.000000 0.000000 Kd 0.640000 0.640000 0.640000 Ks 0.500000 0.500000 0.500000 Ke 0.000000 0.000000 0.000000 Ni 1.000000 d 1.000000 illum 2 -map_Kd CesiumLogoFlat.png +map_Kd cesium.png diff --git a/specs/data/box-missing-texture/box-missing-texture.obj b/specs/data/box-missing-texture/box-missing-texture.obj new file mode 100644 index 0000000..ced632f --- /dev/null +++ b/specs/data/box-missing-texture/box-missing-texture.obj @@ -0,0 +1,46 @@ +# Blender v2.78 (sub 0) OBJ File: 'box.blend' +# www.blender.org +mtllib box-missing-texture.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Material +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 diff --git a/specs/data/box-mtllib/box-mtllib-blue.mtl b/specs/data/box-mtllib/box-mtllib-blue.mtl new file mode 100644 index 0000000..d3fe863 --- /dev/null +++ b/specs/data/box-mtllib/box-mtllib-blue.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'box-multiple-materials.blend' +# Material Count: 1 + +newmtl Blue +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.000000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-mtllib/box-mtllib-green.mtl b/specs/data/box-mtllib/box-mtllib-green.mtl new file mode 100644 index 0000000..89abba9 --- /dev/null +++ b/specs/data/box-mtllib/box-mtllib-green.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'box-multiple-materials.blend' +# Material Count: 1 + +newmtl Green +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.640000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-mtllib/box-mtllib-red.mtl b/specs/data/box-mtllib/box-mtllib-red.mtl new file mode 100644 index 0000000..3721d86 --- /dev/null +++ b/specs/data/box-mtllib/box-mtllib-red.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'box-multiple-materials.blend' +# Material Count: 1 + +newmtl Red +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.000000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-mtllib/box-mtllib.obj b/specs/data/box-mtllib/box-mtllib.obj new file mode 100644 index 0000000..cced063 --- /dev/null +++ b/specs/data/box-mtllib/box-mtllib.obj @@ -0,0 +1,50 @@ +# Blender v2.78 (sub 0) OBJ File: 'box-multiple-materials.blend' +# www.blender.org +mtllib box-mtllib-red.mtl +mtllib box-mtllib-green.mtl box-mtllib-blue.mtl +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn -1.0000 0.0000 0.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 0.0000 0.0000 1.0000 +usemtl Red +f 3/1/1 7/2/1 5/3/1 1/4/1 +usemtl Green +f 1/9/3 2/10/3 4/11/3 3/12/3 +usemtl Blue +f 3/1/5 4/6/5 8/17/5 7/18/5 +usemtl Red +f 8/5/2 4/6/2 2/7/2 6/8/2 +usemtl Green +f 7/13/4 8/14/4 6/15/4 5/16/4 +usemtl Blue +f 5/19/6 6/20/6 2/7/6 1/4/6 diff --git a/specs/data/box-multiple-materials/box-multiple-materials.mtl b/specs/data/box-multiple-materials/box-multiple-materials.mtl new file mode 100644 index 0000000..7fb6cdb --- /dev/null +++ b/specs/data/box-multiple-materials/box-multiple-materials.mtl @@ -0,0 +1,32 @@ +# Blender MTL File: 'box-multiple-materials.blend' +# Material Count: 3 + +newmtl Blue +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.000000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Green +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.640000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Red +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.000000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-multiple-materials/box-multiple-materials.obj b/specs/data/box-multiple-materials/box-multiple-materials.obj new file mode 100644 index 0000000..5c4848d --- /dev/null +++ b/specs/data/box-multiple-materials/box-multiple-materials.obj @@ -0,0 +1,49 @@ +# Blender v2.78 (sub 0) OBJ File: 'box-multiple-materials.blend' +# www.blender.org +mtllib box-multiple-materials.mtl +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +vn -1.0000 0.0000 0.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 0.0000 0.0000 1.0000 +usemtl Red +f 3/1/1 7/2/1 5/3/1 1/4/1 +usemtl Green +f 1/9/3 2/10/3 4/11/3 3/12/3 +usemtl Blue +f 3/1/5 4/6/5 8/17/5 7/18/5 +usemtl Red +f 8/5/2 4/6/2 2/7/2 6/8/2 +usemtl Green +f 7/13/4 8/14/4 6/15/4 5/16/4 +usemtl Blue +f 5/19/6 6/20/6 2/7/6 1/4/6 diff --git a/specs/data/box-negative-indices/box-negative-indices.mtl b/specs/data/box-negative-indices/box-negative-indices.mtl new file mode 100644 index 0000000..abbc294 --- /dev/null +++ b/specs/data/box-negative-indices/box-negative-indices.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'box.blend' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-negative-indices/box-negative-indices.obj b/specs/data/box-negative-indices/box-negative-indices.obj new file mode 100644 index 0000000..e3b2aa6 --- /dev/null +++ b/specs/data/box-negative-indices/box-negative-indices.obj @@ -0,0 +1,20 @@ +# Blender v2.78 (sub 0) OBJ File: 'box.blend' +# www.blender.org +mtllib box-negative-indices.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +usemtl Material +s off +f -8 -7 -5 -6 +f -6 -5 -1 -2 +f -2 -1 -3 -4 +f -4 -3 -7 -8 +f -6 -2 -4 -8 +f -1 -5 -7 -3 diff --git a/specs/data/box-no-materials/box-no-materials.obj b/specs/data/box-no-materials/box-no-materials.obj new file mode 100644 index 0000000..a1f2147 --- /dev/null +++ b/specs/data/box-no-materials/box-no-materials.obj @@ -0,0 +1,125 @@ +# Blender v2.78 (sub 0) OBJ File: 'box-objects.blend' +# www.blender.org +v -1.000000 -1.000000 -4.000000 +v -1.000000 1.000000 -4.000000 +v -1.000000 -1.000000 -6.000000 +v -1.000000 1.000000 -6.000000 +v 1.000000 -1.000000 -4.000000 +v 1.000000 1.000000 -4.000000 +v 1.000000 -1.000000 -6.000000 +v 1.000000 1.000000 -6.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 +v 4.000000 -1.000000 1.000000 +v 4.000000 1.000000 1.000000 +v 4.000000 -1.000000 -1.000000 +v 4.000000 1.000000 -1.000000 +v 6.000000 -1.000000 1.000000 +v 6.000000 1.000000 1.000000 +v 6.000000 -1.000000 -1.000000 +v 6.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +s off +f 9/21/7 10/22/7 12/23/7 11/24/7 +f 11/25/8 12/26/8 16/27/8 15/28/8 +f 15/29/9 16/30/9 14/31/9 13/32/9 +f 13/33/10 14/34/10 10/35/10 9/36/10 +f 11/25/11 15/37/11 13/38/11 9/36/11 +f 16/39/12 12/26/12 10/35/12 14/40/12 +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +s off +f 17/41/13 18/42/13 20/43/13 19/44/13 +f 19/45/14 20/46/14 24/47/14 23/48/14 +f 23/49/15 24/50/15 22/51/15 21/52/15 +f 21/53/16 22/54/16 18/55/16 17/56/16 +f 19/45/17 23/57/17 21/58/17 17/56/17 +f 24/59/18 20/46/18 18/55/18 22/60/18 diff --git a/specs/data/box-normals/box-normals.mtl b/specs/data/box-normals/box-normals.mtl new file mode 100644 index 0000000..abbc294 --- /dev/null +++ b/specs/data/box-normals/box-normals.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'box.blend' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-normals/box-normals.obj b/specs/data/box-normals/box-normals.obj new file mode 100644 index 0000000..5d727cc --- /dev/null +++ b/specs/data/box-normals/box-normals.obj @@ -0,0 +1,26 @@ +# Blender v2.78 (sub 0) OBJ File: 'box.blend' +# www.blender.org +mtllib box-normals.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Material +s off +f 1//1 2//1 4//1 3//1 +f 3//2 4//2 8//2 7//2 +f 7//3 8//3 6//3 5//3 +f 5//4 6//4 2//4 1//4 +f 3//5 7//5 5//5 1//5 +f 8//6 4//6 2//6 6//6 diff --git a/specs/data/box-objects-groups-materials/box-objects-groups-materials.gltf b/specs/data/box-objects-groups-materials/box-objects-groups-materials.gltf new file mode 100644 index 0000000..0220c2a --- /dev/null +++ b/specs/data/box-objects-groups-materials/box-objects-groups-materials.gltf @@ -0,0 +1,486 @@ +{ + "accessors": { + "accessor_0": { + "bufferView": "bufferView_vertex", + "byteOffset": 0, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + -1, + -1, + -6 + ], + "max": [ + 1, + 1, + -4 + ], + "type": "VEC3" + }, + "accessor_1": { + "bufferView": "bufferView_vertex", + "byteOffset": 288, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + -1, + -1, + -1 + ], + "max": [ + 1, + 1, + 1 + ], + "type": "VEC3" + }, + "accessor_2": { + "bufferView": "bufferView_vertex", + "byteOffset": 576, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + 0, + 0 + ], + "max": [ + 1, + 1 + ], + "type": "VEC2" + }, + "accessor_3": { + "bufferView": "bufferView_index", + "byteOffset": 0, + "byteStride": 0, + "componentType": 5123, + "count": 18, + "min": [ + 0 + ], + "max": [ + 11 + ], + "type": "SCALAR" + }, + "accessor_4": { + "bufferView": "bufferView_index", + "byteOffset": 36, + "byteStride": 0, + "componentType": 5123, + "count": 18, + "min": [ + 12 + ], + "max": [ + 23 + ], + "type": "SCALAR" + }, + "accessor_5": { + "bufferView": "bufferView_vertex", + "byteOffset": 768, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + 4, + -1, + -1 + ], + "max": [ + 6, + 1, + 1 + ], + "type": "VEC3" + }, + "accessor_6": { + "bufferView": "bufferView_vertex", + "byteOffset": 1056, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + -1, + -1, + -1 + ], + "max": [ + 1, + 1, + 1 + ], + "type": "VEC3" + }, + "accessor_7": { + "bufferView": "bufferView_vertex", + "byteOffset": 1344, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + 0, + 0 + ], + "max": [ + 1, + 1 + ], + "type": "VEC2" + }, + "accessor_8": { + "bufferView": "bufferView_index", + "byteOffset": 72, + "byteStride": 0, + "componentType": 5123, + "count": 18, + "min": [ + 0 + ], + "max": [ + 11 + ], + "type": "SCALAR" + }, + "accessor_9": { + "bufferView": "bufferView_index", + "byteOffset": 108, + "byteStride": 0, + "componentType": 5123, + "count": 18, + "min": [ + 12 + ], + "max": [ + 23 + ], + "type": "SCALAR" + }, + "accessor_10": { + "bufferView": "bufferView_vertex", + "byteOffset": 1536, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + -1, + -1, + -1 + ], + "max": [ + 1, + 1, + 1 + ], + "type": "VEC3" + }, + "accessor_11": { + "bufferView": "bufferView_vertex", + "byteOffset": 1824, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + -1, + -1, + -1 + ], + "max": [ + 1, + 1, + 1 + ], + "type": "VEC3" + }, + "accessor_12": { + "bufferView": "bufferView_vertex", + "byteOffset": 2112, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + 0, + 0 + ], + "max": [ + 1, + 1 + ], + "type": "VEC2" + }, + "accessor_13": { + "bufferView": "bufferView_index", + "byteOffset": 144, + "byteStride": 0, + "componentType": 5123, + "count": 18, + "min": [ + 0 + ], + "max": [ + 11 + ], + "type": "SCALAR" + }, + "accessor_14": { + "bufferView": "bufferView_index", + "byteOffset": 180, + "byteStride": 0, + "componentType": 5123, + "count": 18, + "min": [ + 12 + ], + "max": [ + 23 + ], + "type": "SCALAR" + } + }, + "asset": { + "generator": "obj2gltf", + "profile": { + "api": "WebGL", + "version": "1.0" + }, + "version": "1.0" + }, + "buffers": { + "buffer": { + "byteLength": 2520, + "uri": "data:application/octet-stream;base64,AACAvwAAgL8AAIDAAACAvwAAgD8AAIDAAACAvwAAgD8AAMDAAACAvwAAgL8AAMDAAACAvwAAgL8AAMDAAACAvwAAgD8AAMDAAACAPwAAgD8AAMDAAACAPwAAgL8AAMDAAACAPwAAgL8AAMDAAACAPwAAgD8AAMDAAACAPwAAgD8AAIDAAACAPwAAgL8AAIDAAACAPwAAgL8AAIDAAACAPwAAgD8AAIDAAACAvwAAgD8AAIDAAACAvwAAgL8AAIDAAACAvwAAgL8AAMDAAACAPwAAgL8AAMDAAACAPwAAgL8AAIDAAACAvwAAgL8AAIDAAACAPwAAgD8AAMDAAACAvwAAgD8AAMDAAACAvwAAgD8AAIDAAACAPwAAgD8AAIDAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAACAQAAAgL8AAIA/AACAQAAAgD8AAIA/AACAQAAAgD8AAIC/AACAQAAAgL8AAIC/AACAQAAAgL8AAIC/AACAQAAAgD8AAIC/AADAQAAAgD8AAIC/AADAQAAAgL8AAIC/AADAQAAAgL8AAIC/AADAQAAAgD8AAIC/AADAQAAAgD8AAIA/AADAQAAAgL8AAIA/AADAQAAAgL8AAIA/AADAQAAAgD8AAIA/AACAQAAAgD8AAIA/AACAQAAAgL8AAIA/AACAQAAAgL8AAIC/AADAQAAAgL8AAIC/AADAQAAAgL8AAIA/AACAQAAAgL8AAIA/AADAQAAAgD8AAIC/AACAQAAAgD8AAIC/AACAQAAAgD8AAIA/AADAQAAAgD8AAIA/AACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAACAvwAAgL8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIA/AACAvwAAgL8AAIA/AACAPwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIA/AACAPwAAgD8AAIA/AACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAAAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAAAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA" + } + }, + "bufferViews": { + "bufferView_vertex": { + "buffer": "buffer", + "byteLength": 2304, + "byteOffset": 0, + "target": 34962 + }, + "bufferView_index": { + "buffer": "buffer", + "byteLength": 216, + "byteOffset": 2304, + "target": 34963 + } + }, + "extensionsUsed": [ + "KHR_materials_common" + ], + "images": {}, + "materials": { + "Blue": { + "extensions": { + "KHR_materials_common": { + "technique": "PHONG", + "values": { + "ambient": [ + 0, + 0, + 0, + 1 + ], + "diffuse": [ + 0, + 0, + 0.64, + 1 + ], + "emission": [ + 0, + 0, + 0, + 1 + ], + "specular": [ + 0.5, + 0.5, + 0.5, + 1 + ], + "shininess": 96.078431, + "transparency": 1, + "transparent": false, + "doubleSided": false + } + } + } + }, + "Green": { + "extensions": { + "KHR_materials_common": { + "technique": "PHONG", + "values": { + "ambient": [ + 0, + 0, + 0, + 1 + ], + "diffuse": [ + 0, + 0.64, + 0, + 1 + ], + "emission": [ + 0, + 0, + 0, + 1 + ], + "specular": [ + 0.5, + 0.5, + 0.5, + 1 + ], + "shininess": 96.078431, + "transparency": 1, + "transparent": false, + "doubleSided": false + } + } + } + }, + "Red": { + "extensions": { + "KHR_materials_common": { + "technique": "PHONG", + "values": { + "ambient": [ + 0, + 0, + 0, + 1 + ], + "diffuse": [ + 0.64, + 0, + 0, + 1 + ], + "emission": [ + 0, + 0, + 0, + 1 + ], + "specular": [ + 0.5, + 0.5, + 0.5, + 1 + ], + "shininess": 96.078431, + "transparency": 1, + "transparent": false, + "doubleSided": false + } + } + } + } + }, + "meshes": { + "CubeBlue_CubeBlue_Blue": { + "name": "CubeBlue_CubeBlue_Blue", + "primitives": [ + { + "attributes": { + "POSITION": "accessor_0", + "NORMAL": "accessor_1", + "TEXCOORD_0": "accessor_2" + }, + "indices": "accessor_3", + "material": "Blue", + "mode": 4 + }, + { + "attributes": { + "POSITION": "accessor_0", + "NORMAL": "accessor_1", + "TEXCOORD_0": "accessor_2" + }, + "indices": "accessor_4", + "material": "Green", + "mode": 4 + } + ] + }, + "CubeGreen_CubeGreen_Green": { + "name": "CubeGreen_CubeGreen_Green", + "primitives": [ + { + "attributes": { + "POSITION": "accessor_5", + "NORMAL": "accessor_6", + "TEXCOORD_0": "accessor_7" + }, + "indices": "accessor_8", + "material": "Green", + "mode": 4 + }, + { + "attributes": { + "POSITION": "accessor_5", + "NORMAL": "accessor_6", + "TEXCOORD_0": "accessor_7" + }, + "indices": "accessor_9", + "material": "Red", + "mode": 4 + } + ] + }, + "CubeRed_CubeRed_Red": { + "name": "CubeRed_CubeRed_Red", + "primitives": [ + { + "attributes": { + "POSITION": "accessor_10", + "NORMAL": "accessor_11", + "TEXCOORD_0": "accessor_12" + }, + "indices": "accessor_13", + "material": "Red", + "mode": 4 + }, + { + "attributes": { + "POSITION": "accessor_10", + "NORMAL": "accessor_11", + "TEXCOORD_0": "accessor_12" + }, + "indices": "accessor_14", + "material": "Blue", + "mode": 4 + } + ] + } + }, + "nodes": { + "Cube": { + "name": "Cube", + "meshes": [ + "CubeBlue_CubeBlue_Blue", + "CubeGreen_CubeGreen_Green", + "CubeRed_CubeRed_Red" + ] + } + }, + "samplers": {}, + "scene": "scene", + "scenes": { + "scene": { + "nodes": [ + "Cube" + ] + } + }, + "textures": {} +} diff --git a/specs/data/box-objects-groups-materials/box-objects-groups-materials.mtl b/specs/data/box-objects-groups-materials/box-objects-groups-materials.mtl new file mode 100644 index 0000000..2f9b11a --- /dev/null +++ b/specs/data/box-objects-groups-materials/box-objects-groups-materials.mtl @@ -0,0 +1,32 @@ +# Blender MTL File: 'box-objects.blend' +# Material Count: 3 + +newmtl Blue +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.000000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Green +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.640000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Red +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.000000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-objects-groups-materials/box-objects-groups-materials.obj b/specs/data/box-objects-groups-materials/box-objects-groups-materials.obj new file mode 100644 index 0000000..1bb7698 --- /dev/null +++ b/specs/data/box-objects-groups-materials/box-objects-groups-materials.obj @@ -0,0 +1,133 @@ +# Blender v2.78 (sub 0) OBJ File: 'box-objects.blend' +# www.blender.org +mtllib box-objects-groups-materials.mtl +o Cube +v -1.000000 -1.000000 -4.000000 +v -1.000000 1.000000 -4.000000 +v -1.000000 -1.000000 -6.000000 +v -1.000000 1.000000 -6.000000 +v 1.000000 -1.000000 -4.000000 +v 1.000000 1.000000 -4.000000 +v 1.000000 -1.000000 -6.000000 +v 1.000000 1.000000 -6.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +g CubeBlue_CubeBlue_Blue +usemtl Blue +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +usemtl Green +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 +v 4.000000 -1.000000 1.000000 +v 4.000000 1.000000 1.000000 +v 4.000000 -1.000000 -1.000000 +v 4.000000 1.000000 -1.000000 +v 6.000000 -1.000000 1.000000 +v 6.000000 1.000000 1.000000 +v 6.000000 -1.000000 -1.000000 +v 6.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +g CubeGreen_CubeGreen_Green +usemtl Green +f 9/21/7 10/22/7 12/23/7 11/24/7 +f 11/25/8 12/26/8 16/27/8 15/28/8 +f 15/29/9 16/30/9 14/31/9 13/32/9 +usemtl Red +f 13/33/10 14/34/10 10/35/10 9/36/10 +f 11/25/11 15/37/11 13/38/11 9/36/11 +f 16/39/12 12/26/12 10/35/12 14/40/12 +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +g CubeRed_CubeRed_Red +usemtl Red +f 17/41/13 18/42/13 20/43/13 19/44/13 +f 19/45/14 20/46/14 24/47/14 23/48/14 +f 23/49/15 24/50/15 22/51/15 21/52/15 +usemtl Blue +f 21/53/16 22/54/16 18/55/16 17/56/16 +f 19/45/17 23/57/17 21/58/17 17/56/17 +f 24/59/18 20/46/18 18/55/18 22/60/18 diff --git a/specs/data/box-objects-groups/box-objects-groups.mtl b/specs/data/box-objects-groups/box-objects-groups.mtl new file mode 100644 index 0000000..2f9b11a --- /dev/null +++ b/specs/data/box-objects-groups/box-objects-groups.mtl @@ -0,0 +1,32 @@ +# Blender MTL File: 'box-objects.blend' +# Material Count: 3 + +newmtl Blue +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.000000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Green +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.640000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Red +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.000000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-objects-groups/box-objects-groups.obj b/specs/data/box-objects-groups/box-objects-groups.obj new file mode 100644 index 0000000..2672d19 --- /dev/null +++ b/specs/data/box-objects-groups/box-objects-groups.obj @@ -0,0 +1,135 @@ +# Blender v2.78 (sub 0) OBJ File: 'box-objects.blend' +# www.blender.org +mtllib box-objects-groups.mtl +o CubeBlue +v -1.000000 -1.000000 -4.000000 +v -1.000000 1.000000 -4.000000 +v -1.000000 -1.000000 -6.000000 +v -1.000000 1.000000 -6.000000 +v 1.000000 -1.000000 -4.000000 +v 1.000000 1.000000 -4.000000 +v 1.000000 -1.000000 -6.000000 +v 1.000000 1.000000 -6.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +g CubeBlue_CubeBlue_Blue +usemtl Blue +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 +o CubeGreen +v 4.000000 -1.000000 1.000000 +v 4.000000 1.000000 1.000000 +v 4.000000 -1.000000 -1.000000 +v 4.000000 1.000000 -1.000000 +v 6.000000 -1.000000 1.000000 +v 6.000000 1.000000 1.000000 +v 6.000000 -1.000000 -1.000000 +v 6.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +g CubeGreen_CubeGreen_Green +usemtl Green +s off +f 9/21/7 10/22/7 12/23/7 11/24/7 +f 11/25/8 12/26/8 16/27/8 15/28/8 +f 15/29/9 16/30/9 14/31/9 13/32/9 +f 13/33/10 14/34/10 10/35/10 9/36/10 +f 11/25/11 15/37/11 13/38/11 9/36/11 +f 16/39/12 12/26/12 10/35/12 14/40/12 +o CubeRed +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +g CubeRed_CubeRed_Red +usemtl Red +s off +f 17/41/13 18/42/13 20/43/13 19/44/13 +f 19/45/14 20/46/14 24/47/14 23/48/14 +f 23/49/15 24/50/15 22/51/15 21/52/15 +f 21/53/16 22/54/16 18/55/16 17/56/16 +f 19/45/17 23/57/17 21/58/17 17/56/17 +f 24/59/18 20/46/18 18/55/18 22/60/18 diff --git a/specs/data/box-objects/box-objects.mtl b/specs/data/box-objects/box-objects.mtl new file mode 100644 index 0000000..2f9b11a --- /dev/null +++ b/specs/data/box-objects/box-objects.mtl @@ -0,0 +1,32 @@ +# Blender MTL File: 'box-objects.blend' +# Material Count: 3 + +newmtl Blue +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.000000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Green +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.640000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Red +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.000000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-objects/box-objects.obj b/specs/data/box-objects/box-objects.obj new file mode 100644 index 0000000..0f86b05 --- /dev/null +++ b/specs/data/box-objects/box-objects.obj @@ -0,0 +1,132 @@ +# Blender v2.78 (sub 0) OBJ File: 'box-objects.blend' +# www.blender.org +mtllib box-objects.mtl +o CubeBlue +v -1.000000 -1.000000 -4.000000 +v -1.000000 1.000000 -4.000000 +v -1.000000 -1.000000 -6.000000 +v -1.000000 1.000000 -6.000000 +v 1.000000 -1.000000 -4.000000 +v 1.000000 1.000000 -4.000000 +v 1.000000 -1.000000 -6.000000 +v 1.000000 1.000000 -6.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Blue +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 +o CubeGreen +v 4.000000 -1.000000 1.000000 +v 4.000000 1.000000 1.000000 +v 4.000000 -1.000000 -1.000000 +v 4.000000 1.000000 -1.000000 +v 6.000000 -1.000000 1.000000 +v 6.000000 1.000000 1.000000 +v 6.000000 -1.000000 -1.000000 +v 6.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Green +s off +f 9/21/7 10/22/7 12/23/7 11/24/7 +f 11/25/8 12/26/8 16/27/8 15/28/8 +f 15/29/9 16/30/9 14/31/9 13/32/9 +f 13/33/10 14/34/10 10/35/10 9/36/10 +f 11/25/11 15/37/11 13/38/11 9/36/11 +f 16/39/12 12/26/12 10/35/12 14/40/12 +o CubeRed +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Red +s off +f 17/41/13 18/42/13 20/43/13 19/44/13 +f 19/45/14 20/46/14 24/47/14 23/48/14 +f 23/49/15 24/50/15 22/51/15 21/52/15 +f 21/53/16 22/54/16 18/55/16 17/56/16 +f 19/45/17 23/57/17 21/58/17 17/56/17 +f 24/59/18 20/46/18 18/55/18 22/60/18 diff --git a/specs/data/box-positions-only/box-positions-only.mtl b/specs/data/box-positions-only/box-positions-only.mtl new file mode 100644 index 0000000..abbc294 --- /dev/null +++ b/specs/data/box-positions-only/box-positions-only.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'box.blend' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-positions-only/box-positions-only.obj b/specs/data/box-positions-only/box-positions-only.obj new file mode 100644 index 0000000..8d2353f --- /dev/null +++ b/specs/data/box-positions-only/box-positions-only.obj @@ -0,0 +1,20 @@ +# Blender v2.78 (sub 0) OBJ File: 'box.blend' +# www.blender.org +mtllib box-positions-only.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +usemtl Material +s off +f 1 2 4 3 +f 3 4 8 7 +f 7 8 6 5 +f 5 6 2 1 +f 3 7 5 1 +f 8 4 2 6 diff --git a/specs/data/box-subdirectories/box-textured.obj b/specs/data/box-subdirectories/box-textured.obj new file mode 100644 index 0000000..faf48db --- /dev/null +++ b/specs/data/box-subdirectories/box-textured.obj @@ -0,0 +1,46 @@ +# Blender v2.78 (sub 0) OBJ File: 'box.blend' +# www.blender.org +mtllib materials/box-textured.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Material +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 diff --git a/specs/data/box-subdirectories/materials/box-textured.mtl b/specs/data/box-subdirectories/materials/box-textured.mtl new file mode 100644 index 0000000..d042c1d --- /dev/null +++ b/specs/data/box-subdirectories/materials/box-textured.mtl @@ -0,0 +1,13 @@ +# Blender MTL File: 'box.blend' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 +map_Kd images/cesium.png diff --git a/specs/data/box-subdirectories/materials/images/cesium.png b/specs/data/box-subdirectories/materials/images/cesium.png new file mode 100644 index 0000000..3b8baee Binary files /dev/null and b/specs/data/box-subdirectories/materials/images/cesium.png differ diff --git a/specs/data/box-textured/box-textured.mtl b/specs/data/box-textured/box-textured.mtl new file mode 100644 index 0000000..c5c879f --- /dev/null +++ b/specs/data/box-textured/box-textured.mtl @@ -0,0 +1,13 @@ +# Blender MTL File: 'box.blend' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 +map_Kd cesium.png diff --git a/specs/data/box-textured/box-textured.obj b/specs/data/box-textured/box-textured.obj new file mode 100644 index 0000000..4f5dc44 --- /dev/null +++ b/specs/data/box-textured/box-textured.obj @@ -0,0 +1,46 @@ +# Blender v2.78 (sub 0) OBJ File: 'box.blend' +# www.blender.org +mtllib box-textured.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Material +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 diff --git a/specs/data/box-textured/cesium.png b/specs/data/box-textured/cesium.png new file mode 100644 index 0000000..3b8baee Binary files /dev/null and b/specs/data/box-textured/cesium.png differ diff --git a/specs/data/box-triangles/box-triangles.mtl b/specs/data/box-triangles/box-triangles.mtl new file mode 100644 index 0000000..abbc294 --- /dev/null +++ b/specs/data/box-triangles/box-triangles.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'box.blend' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-triangles/box-triangles.obj b/specs/data/box-triangles/box-triangles.obj new file mode 100644 index 0000000..124ab2d --- /dev/null +++ b/specs/data/box-triangles/box-triangles.obj @@ -0,0 +1,46 @@ +# Blender v2.78 (sub 0) OBJ File: 'box.blend' +# www.blender.org +mtllib box-triangles.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Material +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 diff --git a/specs/data/box-uncleaned/box-uncleaned.mtl b/specs/data/box-uncleaned/box-uncleaned.mtl new file mode 100644 index 0000000..a304b43 --- /dev/null +++ b/specs/data/box-uncleaned/box-uncleaned.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'None' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-uncleaned/box-uncleaned.obj b/specs/data/box-uncleaned/box-uncleaned.obj new file mode 100644 index 0000000..7558d99 --- /dev/null +++ b/specs/data/box-uncleaned/box-uncleaned.obj @@ -0,0 +1,52 @@ +# Blender v2.78 (sub 0) OBJ File: '' +# www.blender.org +mtllib box-uncleaned.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +g Cube +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +o Cube +g Cube +usemtl Material +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 +g Cube +usemtl Material +o Cube diff --git a/specs/data/box-usemtl/box-usemtl.mtl b/specs/data/box-usemtl/box-usemtl.mtl new file mode 100644 index 0000000..2f9b11a --- /dev/null +++ b/specs/data/box-usemtl/box-usemtl.mtl @@ -0,0 +1,32 @@ +# Blender MTL File: 'box-objects.blend' +# Material Count: 3 + +newmtl Blue +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.000000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Green +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.000000 0.640000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 + +newmtl Red +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.000000 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-usemtl/box-usemtl.obj b/specs/data/box-usemtl/box-usemtl.obj new file mode 100644 index 0000000..2350468 --- /dev/null +++ b/specs/data/box-usemtl/box-usemtl.obj @@ -0,0 +1,129 @@ +# Blender v2.78 (sub 0) OBJ File: 'box-objects.blend' +# www.blender.org +mtllib box-usemtl.mtl +v -1.000000 -1.000000 -4.000000 +v -1.000000 1.000000 -4.000000 +v -1.000000 -1.000000 -6.000000 +v -1.000000 1.000000 -6.000000 +v 1.000000 -1.000000 -4.000000 +v 1.000000 1.000000 -4.000000 +v 1.000000 -1.000000 -6.000000 +v 1.000000 1.000000 -6.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Blue +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 +v 4.000000 -1.000000 1.000000 +v 4.000000 1.000000 1.000000 +v 4.000000 -1.000000 -1.000000 +v 4.000000 1.000000 -1.000000 +v 6.000000 -1.000000 1.000000 +v 6.000000 1.000000 1.000000 +v 6.000000 -1.000000 -1.000000 +v 6.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Green +s off +f 9/21/7 10/22/7 12/23/7 11/24/7 +f 11/25/8 12/26/8 16/27/8 15/28/8 +f 15/29/9 16/30/9 14/31/9 13/32/9 +f 13/33/10 14/34/10 10/35/10 9/36/10 +f 11/25/11 15/37/11 13/38/11 9/36/11 +f 16/39/12 12/26/12 10/35/12 14/40/12 +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Red +s off +f 17/41/13 18/42/13 20/43/13 19/44/13 +f 19/45/14 20/46/14 24/47/14 23/48/14 +f 23/49/15 24/50/15 22/51/15 21/52/15 +f 21/53/16 22/54/16 18/55/16 17/56/16 +f 19/45/17 23/57/17 21/58/17 17/56/17 +f 24/59/18 20/46/18 18/55/18 22/60/18 diff --git a/specs/data/box-uvs/box-uvs.mtl b/specs/data/box-uvs/box-uvs.mtl new file mode 100644 index 0000000..abbc294 --- /dev/null +++ b/specs/data/box-uvs/box-uvs.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'box.blend' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box-uvs/box-uvs.obj b/specs/data/box-uvs/box-uvs.obj new file mode 100644 index 0000000..9beef82 --- /dev/null +++ b/specs/data/box-uvs/box-uvs.obj @@ -0,0 +1,40 @@ +# Blender v2.78 (sub 0) OBJ File: 'box.blend' +# www.blender.org +mtllib box-uvs.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +usemtl Material +s off +f 1/1 2/2 4/3 3/4 +f 3/5 4/6 8/7 7/8 +f 7/9 8/10 6/11 5/12 +f 5/13 6/14 2/15 1/16 +f 3/5 7/17 5/18 1/16 +f 8/19 4/6 2/15 6/20 diff --git a/specs/data/box/box.gltf b/specs/data/box/box.gltf new file mode 100644 index 0000000..0f8ecdb --- /dev/null +++ b/specs/data/box/box.gltf @@ -0,0 +1,176 @@ +{ + "accessors": { + "accessor_0": { + "bufferView": "bufferView_vertex", + "byteOffset": 0, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + -1, + -1, + -1 + ], + "max": [ + 1, + 1, + 1 + ], + "type": "VEC3" + }, + "accessor_1": { + "bufferView": "bufferView_vertex", + "byteOffset": 288, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + -1, + -1, + -1 + ], + "max": [ + 1, + 1, + 1 + ], + "type": "VEC3" + }, + "accessor_2": { + "bufferView": "bufferView_vertex", + "byteOffset": 576, + "byteStride": 0, + "componentType": 5126, + "count": 24, + "min": [ + 0, + 0 + ], + "max": [ + 1, + 1 + ], + "type": "VEC2" + }, + "accessor_3": { + "bufferView": "bufferView_index", + "byteOffset": 0, + "byteStride": 0, + "componentType": 5123, + "count": 36, + "min": [ + 0 + ], + "max": [ + 23 + ], + "type": "SCALAR" + } + }, + "asset": { + "generator": "obj2gltf", + "profile": { + "api": "WebGL", + "version": "1.0" + }, + "version": "1.0" + }, + "buffers": { + "buffer": { + "byteLength": 840, + "uri": "data:application/octet-stream;base64,AACAvwAAgL8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIA/AACAvwAAgL8AAIA/AACAPwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIA/AACAPwAAgD8AAIA/AACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAAAAAAAAgD8AAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AACAPwAAAAAAAAAAAAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/AAAAAAAAAAAAAAAAAAABAAIAAAACAAMABAAFAAYABAAGAAcACAAJAAoACAAKAAsADAANAA4ADAAOAA8AEAARABIAEAASABMAFAAVABYAFAAWABcA" + } + }, + "bufferViews": { + "bufferView_vertex": { + "buffer": "buffer", + "byteLength": 768, + "byteOffset": 0, + "target": 34962 + }, + "bufferView_index": { + "buffer": "buffer", + "byteLength": 72, + "byteOffset": 768, + "target": 34963 + } + }, + "extensionsUsed": [ + "KHR_materials_common" + ], + "images": {}, + "materials": { + "Material": { + "extensions": { + "KHR_materials_common": { + "technique": "PHONG", + "values": { + "ambient": [ + 0, + 0, + 0, + 1 + ], + "diffuse": [ + 0.64, + 0.64, + 0.64, + 1 + ], + "emission": [ + 0, + 0, + 0, + 1 + ], + "specular": [ + 0.5, + 0.5, + 0.5, + 1 + ], + "shininess": 96.078431, + "transparency": 1, + "transparent": false, + "doubleSided": false + } + } + } + } + }, + "meshes": { + "Cube-Mesh": { + "name": "Cube-Mesh", + "primitives": [ + { + "attributes": { + "POSITION": "accessor_0", + "NORMAL": "accessor_1", + "TEXCOORD_0": "accessor_2" + }, + "indices": "accessor_3", + "material": "Material", + "mode": 4 + } + ] + } + }, + "nodes": { + "Cube": { + "name": "Cube", + "meshes": [ + "Cube-Mesh" + ] + } + }, + "samplers": {}, + "scene": "scene", + "scenes": { + "scene": { + "nodes": [ + "Cube" + ] + } + }, + "textures": {} +} \ No newline at end of file diff --git a/specs/data/box/box.mtl b/specs/data/box/box.mtl new file mode 100644 index 0000000..a304b43 --- /dev/null +++ b/specs/data/box/box.mtl @@ -0,0 +1,12 @@ +# Blender MTL File: 'None' +# Material Count: 1 + +newmtl Material +Ns 96.078431 +Ka 0.000000 0.000000 0.000000 +Kd 0.640000 0.640000 0.640000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 diff --git a/specs/data/box/box.obj b/specs/data/box/box.obj new file mode 100644 index 0000000..5246261 --- /dev/null +++ b/specs/data/box/box.obj @@ -0,0 +1,46 @@ +# Blender v2.78 (sub 0) OBJ File: '' +# www.blender.org +mtllib box.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 0.0000 0.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 1.0000 +vt 1.0000 0.0000 +vt 1.0000 1.0000 +vt 0.0000 0.0000 +vt 0.0000 1.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl Material +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 +f 3/5/2 4/6/2 8/7/2 7/8/2 +f 7/9/3 8/10/3 6/11/3 5/12/3 +f 5/13/4 6/14/4 2/15/4 1/16/4 +f 3/5/5 7/17/5 5/18/5 1/16/5 +f 8/19/6 4/6/6 2/15/6 6/20/6 diff --git a/specs/lib/convertSpec.js b/specs/lib/convertSpec.js index a001844..401cb83 100644 --- a/specs/lib/convertSpec.js +++ b/specs/lib/convertSpec.js @@ -1,23 +1,123 @@ 'use strict'; -var Promise = require('bluebird'); - +var fsExtra = require('fs-extra'); var GltfPipeline = require('gltf-pipeline').Pipeline; var path = require('path'); var convert = require('../../lib/convert'); -var objFile = './specs/data/BoxTextured/BoxTextured.obj'; -var gltfFile = './specs/data/BoxTextured/BoxTextured.gltf'; +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'; describe('convert', function() { it('converts an obj to gltf', function(done) { - var spy = spyOn(GltfPipeline, 'processJSONToDisk').and.callFake(function(gltf, outputPath, options, callback) { - return; - }); - expect(convert(objFile, gltfFile, {}) + var spy = spyOn(GltfPipeline, 'processJSONToDisk'); + expect(convert(objPath, gltfPath) .then(function() { var args = spy.calls.first().args; - expect(args[0]).toBeDefined(); - expect(path.normalize(args[1])).toEqual(path.normalize(gltfFile)); + var gltf = args[0]; + var outputPath = args[1]; + expect(path.normalize(outputPath)).toEqual(path.normalize(gltfPath)); + expect(gltf).toBeDefined(); + expect(gltf.images.cesium).toBeDefined(); }), done).toResolve(); }); + + it('uses default gltf-pipeline options', function(done) { + var spy = spyOn(GltfPipeline, 'processJSONToDisk'); + expect(convert(objPath, gltfPath) + .then(function() { + var args = spy.calls.first().args; + var options = args[2]; + expect(options).toEqual({ + createDirectory : false, + basePath : path.dirname(objPath), + binary : false, + embed : true, + embedImage : true, + encodeNormals : false, + quantize : false, + compressTextureCoordinates : false, + aoOptions : undefined, + smoothNormals : false, + optimizeForCesium : false, + textureCompressionOptions : undefined, + preserve : true + }); + }), done).toResolve(); + }); + + it('sets options', function(done) { + var spy = spyOn(GltfPipeline, 'processJSONToDisk'); + var textureCompressionOptions = { + format : 'dxt1', + quality : 10 + }; + var options = { + binary : true, + separate : true, + separateTextures : true, + compress : true, + optimize : true, + generateNormals : true, + ao : true, + optimizeForCesium : true, + textureCompressionOptions : textureCompressionOptions + }; + + expect(convert(objPath, gltfPath, options) + .then(function() { + var args = spy.calls.first().args; + var options = args[2]; + expect(options).toEqual({ + createDirectory : false, + basePath : path.dirname(objPath), + binary : true, + embed : false, + embedImage : false, + encodeNormals : true, + quantize : true, + compressTextureCoordinates : true, + aoOptions : {}, + smoothNormals : true, + optimizeForCesium : true, + textureCompressionOptions : textureCompressionOptions, + preserve : false + }); + }), done).toResolve(); + }); + + it('saves as binary if gltfPath has a .glb extension', function(done) { + var spy = spyOn(GltfPipeline, 'processJSONToDisk'); + expect(convert(objPath, glbPath) + .then(function() { + var args = spy.calls.first().args; + var options = args[2]; + expect(options.binary).toBe(true); + }), done).toResolve(); + }); + + it('bypassPipeline flag bypasses gltf-pipeline', function(done) { + var spy1 = spyOn(convert, '_outputJson'); + var spy2 = 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); + }), done).toResolve(); + }); + + it('throws if objPath is undefined', function() { + expect(function() { + convert(undefined, gltfPath); + }).toThrowDeveloperError(); + }); + + it('throws if gltfPath is undefined', function() { + expect(function() { + convert(objPath, undefined); + }).toThrowDeveloperError(); + }); }); diff --git a/specs/lib/gltfSpec.js b/specs/lib/gltfSpec.js new file mode 100644 index 0000000..3ff7714 --- /dev/null +++ b/specs/lib/gltfSpec.js @@ -0,0 +1,355 @@ +'use strict'; +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 WebGLConstants = Cesium.WebGLConstants; + +var fsExtraReadJson = Promise.promisify(fsExtra.readJson); + +var boxObjUrl = 'specs/data/box/box.obj'; +var groupObjUrl = 'specs/data/box-objects-groups-materials/box-objects-groups-materials.obj'; +var boxGltfUrl = 'specs/data/box/box.gltf'; +var groupGltfUrl = 'specs/data/box-objects-groups-materials/box-objects-groups-materials.gltf'; +var diffuseTextureUrl = 'specs/data/box-textured/cesium.png'; +var transparentDiffuseTextureUrl = 'specs/data/box-complex-material/diffuse.png'; + +describe('gltf', function() { + var boxObjData; + var groupObjData; + var boxGltf; + var groupGltf; + var diffuseTexture; + var transparentDiffuseTexture; + + beforeAll(function(done) { + return Promise.all([ + loadObj(boxObjUrl) + .then(function(data) { + boxObjData = data; + }), + loadObj(groupObjUrl) + .then(function(data) { + groupObjData = data; + }), + fsExtraReadJson(boxGltfUrl) + .then(function(gltf) { + boxGltf = gltf; + }), + fsExtraReadJson(groupGltfUrl) + .then(function(gltf) { + groupGltf = gltf; + }), + loadImage(diffuseTextureUrl) + .then(function(image) { + diffuseTexture = image; + }), + loadImage(transparentDiffuseTextureUrl) + .then(function(image) { + transparentDiffuseTexture = image; + }) + ]).then(done); + }); + + it('simple gltf', function() { + var objData = clone(boxObjData, true); + var gltf = createGltf(objData); + expect(gltf).toEqual(boxGltf); + }); + + it('multiple nodes, meshes, and primitives', function() { + var objData = clone(groupObjData, true); + var gltf = createGltf(objData); + expect(gltf).toEqual(groupGltf); + + expect(Object.keys(gltf.materials).length).toBe(3); + expect(Object.keys(gltf.nodes).length).toBe(1); + expect(Object.keys(gltf.meshes).length).toBe(3); + + // Check for two primitives in each mesh + for (var id in gltf.meshes) { + if (gltf.meshes.hasOwnProperty(id)) { + var mesh = gltf.meshes[id]; + expect(mesh.primitives.length).toBe(2); + } + } + }); + + it('sets default material values', function() { + var objData = clone(boxObjData, true); + objData.materials.Material = {}; + + var gltf = createGltf(objData); + var material = gltf.materials.Material; + var kmc = material.extensions.KHR_materials_common; + var values = kmc.values; + + expect(kmc.technique).toBe('LAMBERT'); + expect(values.ambient).toEqual([0.0, 0.0, 0.0, 1]); + expect(values.diffuse).toEqual([0.5, 0.5, 0.5, 1]); + expect(values.emission).toEqual([0.0, 0.0, 0.0, 1]); + expect(values.specular).toEqual([0.0, 0.0, 0.0, 1]); + expect(values.shininess).toEqual(0.0); + }); + + it('sets material for diffuse texture', function() { + var objData = clone(boxObjData, true); + objData.materials.Material = { + diffuseColorMap : diffuseTextureUrl + }; + objData.images[diffuseTextureUrl] = diffuseTexture; + + var gltf = createGltf(objData); + var kmc = gltf.materials.Material.extensions.KHR_materials_common; + var texture = gltf.textures.texture_cesium; + var image = gltf.images.cesium; + + expect(kmc.technique).toBe('LAMBERT'); + expect(kmc.values.diffuse).toEqual('texture_cesium'); + expect(kmc.values.transparency).toBe(1.0); + expect(kmc.values.transparent).toBe(false); + expect(kmc.values.doubleSided).toBe(false); + + expect(texture).toEqual({ + format : WebGLConstants.RGB, + internalFormat : WebGLConstants.RGB, + sampler : 'sampler', + source : 'cesium', + target : WebGLConstants.TEXTURE_2D, + type : WebGLConstants.UNSIGNED_BYTE + }); + + expect(image).toBeDefined(); + expect(image.name).toBe('cesium'); + expect(image.uri.indexOf('data:image/png;base64,') >= 0).toBe(true); + + expect(gltf.samplers.sampler).toEqual({ + magFilter : WebGLConstants.LINEAR, + minFilter : WebGLConstants.LINEAR, + wrapS : WebGLConstants.REPEAT, + wrapT : WebGLConstants.REPEAT + }); + }); + + it('sets material for alpha less than 1', function() { + var objData = clone(boxObjData, true); + objData.materials.Material = { + alpha : 0.4 + }; + + var gltf = createGltf(objData); + 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.transparency).toBe(1.0); + expect(kmc.values.transparent).toBe(true); + expect(kmc.values.doubleSided).toBe(true); + }); + + 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 gltf = createGltf(objData); + var kmc = gltf.materials.Material.extensions.KHR_materials_common; + + expect(kmc.values.diffuse).toEqual('texture_cesium'); + expect(kmc.values.transparency).toBe(0.4); + expect(kmc.values.transparent).toBe(true); + expect(kmc.values.doubleSided).toBe(true); + }); + + it('sets material for transparent diffuse texture', function() { + var objData = clone(boxObjData, true); + objData.materials.Material = { + diffuseColorMap : transparentDiffuseTextureUrl + }; + objData.images[transparentDiffuseTextureUrl] = transparentDiffuseTexture; + + var gltf = createGltf(objData); + var kmc = gltf.materials.Material.extensions.KHR_materials_common; + + expect(kmc.values.diffuse).toBe('texture_diffuse'); + expect(kmc.values.transparency).toBe(1.0); + expect(kmc.values.transparent).toBe(true); + expect(kmc.values.doubleSided).toBe(true); + }); + + 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 gltf = createGltf(objData); + var kmc = gltf.materials.Material.extensions.KHR_materials_common; + + expect(kmc.technique).toBe('PHONG'); + expect(kmc.values.specular).toEqual([0.1, 0.1, 0.2, 1]); + expect(kmc.values.shininess).toEqual(0.1); + }); + + 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; + + var gltf = createGltf(objData); + var kmc = gltf.materials.Material.extensions.KHR_materials_common; + + expect(kmc.technique).toBe('CONSTANT'); + expect(kmc.values.emission).toEqual('texture_cesium'); + }); + + it('sets default material when texture is missing', function() { + var objData = clone(boxObjData, true); + objData.materials.Material = { + diffuseColorMap : diffuseTextureUrl + }; + + var gltf = createGltf(objData); + 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; + + // Creates a material called "default" + var gltf = createGltf(objData); + 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 = {}; + + // Uses the original name of the material + var gltf = createGltf(objData); + 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() { + // 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; + + var gltf = createGltf(objData); + 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'); + + // 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; + + gltf = createGltf(objData); + kmc1 = gltf.materials.Material.extensions.KHR_materials_common; + 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; + + var gltf = createGltf(objData); + var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes; + expect(attributes.POSITION).toBeDefined(); + expect(attributes.NORMAL).toBeUndefined(); + expect(attributes.TEXCOORD_0).toBeDefined(); + }); + + it('runs without uvs', function() { + var objData = clone(boxObjData, true); + objData.nodes[0].meshes[0].uvs.length = 0; + + var gltf = createGltf(objData); + var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes; + expect(attributes.POSITION).toBeDefined(); + expect(attributes.NORMAL).toBeDefined(); + expect(attributes.TEXCOORD_0).toBeUndefined(); + }); + + 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; + + var gltf = createGltf(objData); + var attributes = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0].attributes; + expect(attributes.POSITION).toBeDefined(); + expect(attributes.NORMAL).toBeUndefined(); + expect(attributes.TEXCOORD_0).toBeUndefined(); + }); + + function expandObjData(objData, duplicatesLength) { + var mesh = objData.nodes[0].meshes[0]; + var indices = mesh.primitives[0].indices; + var positions = mesh.positions; + var normals = mesh.normals; + var uvs = mesh.uvs; + + var indicesLength = indices.length; + var vertexCount = positions.length / 3; + + for (var i = 1; i < duplicatesLength; ++i) { + for (var j = 0; j < vertexCount; ++j) { + positions.push(0.0); + positions.push(0.0); + positions.push(0.0); + normals.push(0.0); + normals.push(0.0); + normals.push(0.0); + uvs.push(0.0); + uvs.push(0.0); + } + for (var k = 0; k < indicesLength; ++k) { + indices.push(indices.get(k) + vertexCount * i); + } + } + } + + 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]; + var indicesLength = mesh.primitives[0].indices.length; + var vertexCount = mesh.positions.length / 3; + + var gltf = createGltf(objData); + var primitive = gltf.meshes[Object.keys(gltf.meshes)[0]].primitives[0]; + var indicesAccessor = gltf.accessors[primitive.indices]; + expect(indicesAccessor.count).toBe(indicesLength); + expect(indicesAccessor.max[0]).toBe(vertexCount - 1); + expect(indicesAccessor.componentType).toBe(WebGLConstants.UNSIGNED_INT); + + var positionAccessor = gltf.accessors[primitive.attributes.POSITION]; + expect(positionAccessor.count).toBe(vertexCount); + }); +}); diff --git a/specs/lib/imageSpec.js b/specs/lib/imageSpec.js new file mode 100644 index 0000000..cb5359c --- /dev/null +++ b/specs/lib/imageSpec.js @@ -0,0 +1,91 @@ +'use strict'; +var Cesium = require('cesium'); +var path = require('path'); +var loadImage = require('../../lib/image.js'); + +var WebGLConstants = Cesium.WebGLConstants; + +var pngImage = 'specs/data/box-complex-material/shininess.png'; +var jpgImage = 'specs/data/box-complex-material/emission.jpg'; +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 invalidImage = 'invalid.png'; + +describe('image', function() { + it('loads png image', function(done) { + expect(loadImage(pngImage) + .then(function(info) { + expect(info.transparent).toBe(false); + expect(info.channels).toBe(3); + expect(info.data).toBeDefined(); + expect(info.uri.indexOf('data:image/png') === 0).toBe(true); + expect(info.format).toBe(WebGLConstants.RGB); + }), done).toResolve(); + }); + + it('loads jpg image', function(done) { + expect(loadImage(jpgImage) + .then(function(info) { + expect(info.transparent).toBe(false); + expect(info.channels).toBe(3); + expect(info.data).toBeDefined(); + expect(info.uri.indexOf('data:image/jpeg') === 0).toBe(true); + expect(info.format).toBe(WebGLConstants.RGB); + }), done).toResolve(); + }); + + it('loads jpeg image', function(done) { + expect(loadImage(jpegImage) + .then(function(info) { + expect(info.transparent).toBe(false); + expect(info.channels).toBe(3); + expect(info.data).toBeDefined(); + expect(info.uri.indexOf('data:image/jpeg') === 0).toBe(true); + expect(info.format).toBe(WebGLConstants.RGB); + }), done).toResolve(); + }); + + it('loads gif image', function(done) { + expect(loadImage(gifImage) + .then(function(info) { + expect(info.transparent).toBe(false); + expect(info.channels).toBe(3); + expect(info.data).toBeDefined(); + expect(info.uri.indexOf('data:image/gif') === 0).toBe(true); + expect(info.format).toBe(WebGLConstants.RGB); + }), done).toResolve(); + }); + + it('loads grayscale image', function(done) { + expect(loadImage(grayscaleImage) + .then(function(info) { + expect(info.transparent).toBe(false); + expect(info.channels).toBe(1); + expect(info.data).toBeDefined(); + expect(info.uri.indexOf('data:image/png') === 0).toBe(true); + expect(info.format).toBe(WebGLConstants.ALPHA); + }), done).toResolve(); + }); + + it('loads transparentImage image', function(done) { + expect(loadImage(transparentImage) + .then(function(info) { + expect(info.transparent).toBe(true); + expect(info.channels).toBe(4); + expect(info.data).toBeDefined(); + expect(info.uri.indexOf('data:image/png') === 0).toBe(true); + expect(info.format).toBe(WebGLConstants.RGBA); + }), done).toResolve(); + }); + + it('handles invalid image file', function(done) { + spyOn(console, 'log'); + expect(loadImage(invalidImage) + .then(function(image) { + expect(image).toBeUndefined(); + expect(console.log.calls.argsFor(0)[0].indexOf('Could not read image file') >= 0).toBe(true); + }), done).toResolve(); + }); +}); diff --git a/specs/lib/mtlSpec.js b/specs/lib/mtlSpec.js new file mode 100644 index 0000000..36be197 --- /dev/null +++ b/specs/lib/mtlSpec.js @@ -0,0 +1,53 @@ +'use strict'; +var path = require('path'); +var loadMtl = require('../../lib/mtl.js'); + +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)); +} + +describe('mtl', function() { + it('loads complex material', function(done) { + expect(loadMtl(complexMaterialUrl) + .then(function(materials) { + var material = materials.Material; + expect(material).toBeDefined(); + expect(material.ambientColor).toEqual([0.2, 0.2, 0.2, 1.0]); + expect(material.emissionColor).toEqual([0.1, 0.1, 0.1, 1.0]); + expect(material.diffuseColor).toEqual([0.64, 0.64, 0.64, 1.0]); + 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.specularShininessMap).toEqual(getImagePath(complexMaterialUrl, 'shininess.png')); + expect(material.normalMap).toEqual(getImagePath(complexMaterialUrl, 'bump.png')); + expect(material.alphaMap).toEqual(getImagePath(complexMaterialUrl, 'alpha.png')); + }), done).toResolve(); + }); + + it('loads mtl with multiple materials', function(done) { + expect(loadMtl(multipleMaterialsUrl) + .then(function(materials) { + expect(Object.keys(materials).length).toBe(3); + expect(materials.Red.diffuseColor).toEqual([0.64, 0.0, 0.0, 1.0]); + expect(materials.Green.diffuseColor).toEqual([0.0, 0.64, 0.0, 1.0]); + expect(materials.Blue.diffuseColor).toEqual([0.0, 0.0, 0.64, 1.0]); + }), done).toResolve(); + }); + + it('handles invalid mtl file', function(done) { + spyOn(console, 'log'); + expect(loadMtl(invalidMaterialUrl) + .then(function(materials) { + expect(materials).toEqual({}); + expect(console.log.calls.argsFor(0)[0].indexOf('Could not read material file') >= 0).toBe(true); + }), done).toResolve(); + }); +}); diff --git a/specs/lib/objSpec.js b/specs/lib/objSpec.js new file mode 100644 index 0000000..89f0f91 --- /dev/null +++ b/specs/lib/objSpec.js @@ -0,0 +1,328 @@ +'use strict'; +var Cesium = require('cesium'); +var path = require('path'); +var Promise = require('bluebird'); +var loadObj = require('../../lib/obj.js'); + +var RuntimeError = Cesium.RuntimeError; + +var objUrl = 'specs/data/box/box.obj'; +var objNormalsUrl = 'specs/data/box-normals/box-normals.obj'; +var objUvsUrl = 'specs/data/box-uvs/box-uvs.obj'; +var objPositionsOnlyUrl = 'specs/data/box-positions-only/box-positions-only.obj'; +var objNegativeIndicesUrl = 'specs/data/box-negative-indices/box-negative-indices.obj'; +var objTrianglesUrl = 'specs/data/box-triangles/box-triangles.obj'; +var objObjectsUrl = 'specs/data/box-objects/box-objects.obj'; +var objGroupsUrl = 'specs/data/box-groups/box-groups.obj'; +var objObjectsGroupsUrl = 'specs/data/box-objects-groups/box-objects-groups.obj'; +var objUsemtlUrl = 'specs/data/box-usemtl/box-usemtl.obj'; +var objNoMaterialsUrl = 'specs/data/box-no-materials/box-no-materials.obj'; +var objMultipleMaterialsUrl = 'specs/data/box-multiple-materials/box-multiple-materials.obj'; +var objUncleanedUrl = 'specs/data/box-uncleaned/box-uncleaned.obj'; +var objMtllibUrl = 'specs/data/box-mtllib/box-mtllib.obj'; +var objMissingMtllibUrl = 'specs/data/box-missing-mtllib/box-missing-mtllib.obj'; +var objTexturedUrl = 'specs/data/box-textured/box-textured.obj'; +var objMissingTextureUrl = 'specs/data/box-missing-texture/box-missing-texture.obj'; +var objSubdirectoriesUrl = 'specs/data/box-subdirectories/box-textured.obj'; +var objComplexMaterialUrl = 'specs/data/box-complex-material/box-complex-material.obj'; +var objInvalidContentsUrl = 'specs/data/box/box.mtl'; +var objInvalidUrl = 'invalid.obj'; + +function getMeshes(data) { + var meshes = []; + var nodes = data.nodes; + var nodesLength = nodes.length; + for (var i = 0; i < nodesLength; ++i) { + meshes = meshes.concat(nodes[i].meshes); + } + return meshes; +} + +function getPrimitives(data) { + var primitives = []; + var nodes = data.nodes; + var nodesLength = nodes.length; + for (var i = 0; i < nodesLength; ++i) { + var meshes = nodes[i].meshes; + var meshesLength = meshes.length; + for (var j = 0; j < meshesLength; ++j) { + primitives = primitives.concat(meshes[j].primitives); + } + } + return primitives; +} + +function getImagePath(objPath, relativePath) { + return path.normalize(path.join(path.dirname(objPath), relativePath)); +} + +describe('obj', function() { + it('loads obj with positions, normals, and uvs', function(done) { + expect(loadObj(objUrl) + .then(function(data) { + var images = data.images; + var materials = data.materials; + var nodes = data.nodes; + var meshes = getMeshes(data); + var primitives = getPrimitives(data); + + expect(Object.keys(images).length).toBe(0); + expect(materials.Material).toBeDefined(); + expect(nodes.length).toBe(1); + expect(meshes.length).toBe(1); + expect(primitives.length).toBe(1); + + var node = nodes[0]; + var mesh = meshes[0]; + var primitive = primitives[0]; + + expect(node.name).toBe('Cube'); + expect(mesh.name).toBe('Cube-Mesh'); + expect(mesh.positions.length / 3).toBe(24); + expect(mesh.normals.length / 3).toBe(24); + expect(mesh.uvs.length / 2).toBe(24); + expect(primitive.indices.length).toBe(36); + expect(primitive.material).toBe('Material'); + }), done).toResolve(); + }); + + it('loads obj with normals', function(done) { + expect(loadObj(objNormalsUrl) + .then(function(data) { + var mesh = getMeshes(data)[0]; + expect(mesh.positions.length / 3).toBe(24); + expect(mesh.normals.length / 3).toBe(24); + expect(mesh.uvs.length / 2).toBe(0); + }), done).toResolve(); + }); + + it('loads obj with uvs', function(done) { + expect(loadObj(objUvsUrl) + .then(function(data) { + var mesh = getMeshes(data)[0]; + expect(mesh.positions.length / 3).toBe(20); + expect(mesh.normals.length / 3).toBe(0); + expect(mesh.uvs.length / 2).toBe(20); + }), done).toResolve(); + }); + + it('loads obj with negative indices', function(done) { + expect(Promise.all([ + loadObj(objPositionsOnlyUrl), + loadObj(objNegativeIndicesUrl) + ]) + .then(function(results) { + var positionsReference = getMeshes(results[0])[0].positions.toFloatBuffer(); + var positions = getMeshes(results[1])[0].positions.toFloatBuffer(); + expect(positions).toEqual(positionsReference); + }), done).toResolve(); + }); + + it('loads obj with triangle faces', function(done) { + expect(loadObj(objTrianglesUrl) + .then(function(data) { + var mesh = getMeshes(data)[0]; + var primitive = getPrimitives(data)[0]; + expect(mesh.positions.length / 3).toBe(24); + expect(primitive.indices.length).toBe(36); + }), done).toResolve(); + }); + + it('loads obj with triangle faces', function(done) { + expect(loadObj(objTrianglesUrl) + .then(function(data) { + var mesh = getMeshes(data)[0]; + var primitive = getPrimitives(data)[0]; + expect(mesh.positions.length / 3).toBe(24); + expect(primitive.indices.length).toBe(36); + }), done).toResolve(); + }); + + it('loads obj with objects', function(done) { + expect(loadObj(objObjectsUrl) + .then(function(data) { + var nodes = data.nodes; + expect(nodes.length).toBe(3); + expect(nodes[0].name).toBe('CubeBlue'); + expect(nodes[1].name).toBe('CubeGreen'); + expect(nodes[2].name).toBe('CubeRed'); + + var primitives = getPrimitives(data); + expect(primitives.length).toBe(3); + expect(primitives[0].material).toBe('Blue'); + expect(primitives[1].material).toBe('Green'); + expect(primitives[2].material).toBe('Red'); + }), done).toResolve(); + }); + + it('loads obj with groups', function(done) { + expect(loadObj(objGroupsUrl) + .then(function(data) { + var nodes = data.nodes; + expect(nodes.length).toBe(3); + expect(nodes[0].name).toBe('CubeBlue'); + expect(nodes[1].name).toBe('CubeGreen'); + expect(nodes[2].name).toBe('CubeRed'); + + var primitives = getPrimitives(data); + expect(primitives.length).toBe(3); + expect(primitives[0].material).toBe('Blue'); + expect(primitives[1].material).toBe('Green'); + expect(primitives[2].material).toBe('Red'); + }), done).toResolve(); + }); + + it('loads obj with objects and groups', function(done) { + expect(loadObj(objObjectsGroupsUrl) + .then(function(data) { + var nodes = data.nodes; + expect(nodes.length).toBe(3); + expect(nodes[0].name).toBe('CubeBlue'); + expect(nodes[1].name).toBe('CubeGreen'); + expect(nodes[2].name).toBe('CubeRed'); + + var meshes = getMeshes(data); + expect(meshes.length).toBe(3); + expect(meshes[0].name).toBe('CubeBlue_CubeBlue_Blue'); + expect(meshes[1].name).toBe('CubeGreen_CubeGreen_Green'); + expect(meshes[2].name).toBe('CubeRed_CubeRed_Red'); + + var primitives = getPrimitives(data); + expect(primitives.length).toBe(3); + expect(primitives[0].material).toBe('Blue'); + expect(primitives[1].material).toBe('Green'); + expect(primitives[2].material).toBe('Red'); + }), done).toResolve(); + }); + + it('loads obj with usemtl only', function(done) { + expect(loadObj(objUsemtlUrl) + .then(function(data) { + var nodes = data.nodes; + expect(nodes.length).toBe(1); + expect(nodes[0].name).toBe('Node'); // default name + + var meshes = getMeshes(data); + expect(meshes.length).toBe(1); + expect(meshes[0].name).toBe('Node-Mesh'); + + var primitives = getPrimitives(data); + expect(primitives.length).toBe(3); + expect(primitives[0].material).toBe('Blue'); + expect(primitives[1].material).toBe('Green'); + expect(primitives[2].material).toBe('Red'); + }), done).toResolve(); + }); + + it('loads obj with no materials', function(done) { + expect(loadObj(objNoMaterialsUrl) + .then(function(data) { + var nodes = data.nodes; + expect(nodes.length).toBe(1); + expect(nodes[0].name).toBe('Node'); // default name + + var primitives = getPrimitives(data); + expect(primitives.length).toBe(1); + }), done).toResolve(); + }); + + it('loads obj with multiple materials', function(done) { + // The usemtl markers are interleaved, but should condense to just three primitives + expect(loadObj(objMultipleMaterialsUrl) + .then(function(data) { + var nodes = data.nodes; + expect(nodes.length).toBe(1); + + var primitives = getPrimitives(data); + expect(primitives.length).toBe(3); + + expect(primitives[0].indices.length).toBe(12); + expect(primitives[1].indices.length).toBe(12); + expect(primitives[2].indices.length).toBe(12); + expect(primitives[0].material).toBe('Red'); + expect(primitives[1].material).toBe('Green'); + expect(primitives[2].material).toBe('Blue'); + }), done).toResolve(); + }); + + 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) + .then(function(data) { + var nodes = data.nodes; + var meshes = getMeshes(data); + var primitives = getPrimitives(data); + + expect(nodes.length).toBe(1); + expect(meshes.length).toBe(1); + expect(primitives.length).toBe(1); + + expect(nodes[0].name).toBe('Cube'); + expect(meshes[0].name).toBe('Cube_1'); + }), done).toResolve(); + }); + + it('loads obj with multiple mtllibs', function(done) { + expect(loadObj(objMtllibUrl) + .then(function(data) { + var materials = data.materials; + expect(Object.keys(materials).length).toBe(3); + expect(materials.Red.diffuseColor).toEqual([0.64, 0.0, 0.0, 1.0]); + expect(materials.Green.diffuseColor).toEqual([0.0, 0.64, 0.0, 1.0]); + expect(materials.Blue.diffuseColor).toEqual([0.0, 0.0, 0.64, 1.0]); + }), done).toResolve(); + }); + + it('loads obj with missing mtllib', function(done) { + spyOn(console, 'log'); + expect(loadObj(objMissingMtllibUrl) + .then(function(data) { + expect(data.materials).toEqual({}); + }), done).toResolve(); + }); + + it('loads obj with texture', function(done) { + expect(loadObj(objTexturedUrl) + .then(function(data) { + var imagePath = getImagePath(objTexturedUrl, 'cesium.png'); + expect(data.images[imagePath]).toBeDefined(); + expect(data.materials.Material.diffuseColorMap).toEqual(imagePath); + }), done).toResolve(); + }); + + it('loads obj with missing texture', function(done) { + spyOn(console, 'log'); + expect(loadObj(objMissingTextureUrl) + .then(function(data) { + var imagePath = getImagePath(objMissingTextureUrl, 'cesium.png'); + expect(data.images[imagePath]).toBeUndefined(); + expect(data.materials.Material.diffuseColorMap).toEqual(imagePath); + }), done).toResolve(); + }); + + it('loads obj with subdirectories', function(done) { + expect(loadObj(objSubdirectoriesUrl) + .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); + }), done).toResolve(); + }); + + it('loads obj with complex material', function(done) { + expect(loadObj(objComplexMaterialUrl) + .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 + }), done).toResolve(); + }); + + it('does not process file with invalid contents', function(done) { + expect(loadObj(objInvalidContentsUrl), done).toRejectWith(RuntimeError); + }); + + it('throw when reading invalid file', function(done) { + expect(loadObj(objInvalidUrl), done).toRejectWith(Error); + }); +});