Refactor and tests

This commit is contained in:
Sean Lilley 2017-03-13 15:28:51 -04:00
parent d8408e3f2c
commit b8b118bca0
75 changed files with 4650 additions and 952 deletions

10
.gitignore vendored
View File

@ -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

View File

@ -2,7 +2,7 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/OBJ2GLTF.iml" filepath="$PROJECT_DIR$/.idea/OBJ2GLTF.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/obj2gltf.iml" filepath="$PROJECT_DIR$/.idea/obj2gltf.iml" />
</modules>
</component>
</project>

View File

@ -2,6 +2,7 @@
/doc
/specs
/test
/output
/typings
/coverage
.jshintrc

View File

@ -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

View File

@ -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.

View File

@ -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.
<p align="center">
<a href="http://cesiumjs.org/"><img src="doc/cesium.png" /></a>
<a href="http://cesiumjs.org/"><img src="doc/cesium.png" onerror="this.src='cesium.png'"/></a>
</p>

View File

@ -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);
});

View File

@ -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]

107
lib/ArrayStorage.js Normal file
View File

@ -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
};
};

54
lib/clone.js Normal file
View File

@ -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;
}

View File

@ -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;
});
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}

27
lib/readLines.js Normal file
View File

@ -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);
}));
});
}

View File

@ -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",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -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

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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": {}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@ -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

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

176
specs/data/box/box.gltf Normal file
View File

@ -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": {}
}

12
specs/data/box/box.mtl Normal file
View File

@ -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

46
specs/data/box/box.obj Normal file
View File

@ -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

View File

@ -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();
});
});

355
specs/lib/gltfSpec.js Normal file
View File

@ -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);
});
});

91
specs/lib/imageSpec.js Normal file
View File

@ -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();
});
});

53
specs/lib/mtlSpec.js Normal file
View File

@ -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();
});
});

328
specs/lib/objSpec.js Normal file
View File

@ -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);
});
});