From dc84a547a400e6d8262d90d86cb2fd1ffdce37c8 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Sat, 28 Apr 2018 21:42:23 -0400 Subject: [PATCH] More tests --- README.md | 2 +- bin/gltf-pipeline.js | 120 ++-- index.js | 10 +- lib/addDefaults.js | 64 +- lib/compressDracoMeshes.js | 55 +- lib/findAccessorMinMax.js | 8 +- lib/getAccessorByteStride.js | 9 +- lib/getComponentReader.js | 15 +- lib/getStatistics.js | 8 +- lib/gltfToGlb.js | 6 +- lib/mergeBuffers.js | 37 +- lib/processGltf.js | 14 +- lib/readAccessorPacked.js | 20 +- lib/removeUnusedElements.js | 52 +- lib/updateVersion.js | 203 +++--- lib/writeResources.js | 20 +- .../box-textured-separate.gltf | 1 - specs/lib/addDefaultsSpec.js | 168 ++--- specs/lib/addPipelineExtrasSpec.js | 24 +- specs/lib/addToArraySpec.js | 6 +- specs/lib/compressDracoMeshesSpec.js | 7 + specs/lib/findAccessorMinMaxSpec.js | 33 +- specs/lib/getAccessorByteStrideSpec.js | 72 +-- specs/lib/getComponentReaderSpec.js | 30 + specs/lib/getImageExtensionSpec.js | 12 +- specs/lib/getJsonBufferPaddedSpec.js | 13 +- specs/lib/getStatisticsSpec.js | 58 +- specs/lib/glbToGltfSpec.js | 18 +- specs/lib/gltfToGlbSpec.js | 17 +- specs/lib/hasExtensionSpec.js | 14 +- specs/lib/mergeBuffersSpec.js | 53 +- specs/lib/numberOfComponentsForTypeSpec.js | 7 + specs/lib/parseGlbSpec.js | 142 +++- specs/lib/processGlbSpec.js | 17 +- specs/lib/processGltfSpec.js | 19 +- specs/lib/readAccessorPackedSpec.js | 87 +++ specs/lib/readResourcesSpec.js | 6 +- specs/lib/removeDefaultsSpec.js | 20 + specs/lib/removeExtensionsRequiredSpec.js | 21 +- specs/lib/removeExtensionsUsedSpec.js | 23 +- specs/lib/removePipelineExtrasSpec.js | 41 +- specs/lib/removeUnusedElementsSpec.js | 306 +++++++++ specs/lib/updateVersionSpec.js | 610 +++++++++++++++++- specs/lib/writeResourcesSpec.js | 26 +- 44 files changed, 1966 insertions(+), 528 deletions(-) create mode 100644 specs/lib/compressDracoMeshesSpec.js create mode 100644 specs/lib/getComponentReaderSpec.js create mode 100644 specs/lib/readAccessorPackedSpec.js create mode 100644 specs/lib/removeUnusedElementsSpec.js diff --git a/README.md b/README.md index b1666bed..101e7c35 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ var fsExtra = require('fs-extra'); var gltfToGlb = gltfPipeline.gltfToGlb; var gltf = fsExtra.readJsonSync('model.gltf'); var options = { - separateTextures : true + separateTextures: true }; processGltf(gltf, options) .then(function(results) { diff --git a/bin/gltf-pipeline.js b/bin/gltf-pipeline.js index 07567ce3..448a47e4 100644 --- a/bin/gltf-pipeline.js +++ b/bin/gltf-pipeline.js @@ -25,57 +25,57 @@ var argv = yargs .help('h') .alias('h', 'help') .options({ - input : { - alias : 'i', - describe : 'Path to the glTF or glb file.', - type : 'string', - normalize : true, - demandOption : true - }, - output : { - alias : 'o', - describe : 'Output path of the glTF or glb file. Separate resources will be saved to the same directory.', - type : 'string', - normalize : true - }, - binary : { - alias : 'b', - describe : 'Convert the input glTF to glb.', - type : 'boolean', - default : false - }, - json : { - alias : 'j', - describe : 'Convert the input glb to glTF.', - type : 'boolean', - default : false - }, - separate : { - alias : 's', - describe : 'Write separate buffers, shaders, and textures instead of embedding them in the glTF.', - type : 'boolean', - default : defaults.separate - }, - separateTextures : { - alias : 't', - describe : 'Write out separate textures only.', - type : 'boolean', - default : defaults.separateTextures - }, - checkTransparency : { - describe : 'Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures are considered to be opaque.', - type : 'boolean', - default : defaults.checkTransparency - }, - secure : { - describe : 'Prevent the converter from reading textures or mtl files outside of the input obj directory.', - type : 'boolean', - default : defaults.secure - }, - stats : { - describe : 'Print statistics to console for input and output glTF files.', - type : 'boolean', - default : defaults.stats + input: { + alias: 'i', + describe: 'Path to the glTF or glb file.', + type: 'string', + normalize: true, + demandOption: true + }, + output: { + alias: 'o', + describe: 'Output path of the glTF or glb file. Separate resources will be saved to the same directory.', + type: 'string', + normalize: true + }, + binary: { + alias: 'b', + describe: 'Convert the input glTF to glb.', + type: 'boolean', + default: false + }, + json: { + alias: 'j', + describe: 'Convert the input glb to glTF.', + type: 'boolean', + default: false + }, + separate: { + alias: 's', + describe: 'Write separate buffers, shaders, and textures instead of embedding them in the glTF.', + type: 'boolean', + default: defaults.separate + }, + separateTextures: { + alias: 't', + describe: 'Write out separate textures only.', + type: 'boolean', + default: defaults.separateTextures + }, + checkTransparency: { + describe: 'Do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. By default textures are considered to be opaque.', + type: 'boolean', + default: defaults.checkTransparency + }, + secure: { + describe: 'Prevent the converter from reading textures or mtl files outside of the input directory.', + type: 'boolean', + default: defaults.secure + }, + stats: { + describe: 'Print statistics to console for input and output glTF files.', + type: 'boolean', + default: defaults.stats }, 'draco.compressMeshes': { alias: 'd', @@ -107,7 +107,7 @@ var argv = yargs type: 'number' }, 'draco.unifiedQuantization': { - default : false, + default: false, describe: 'Quantize positions of all primitives using the same quantization grid defined by the unified bounding box of all primitives. If this option is not set, quantization is applied on each primitive separately which can result in gaps appearing between different primitives. Default is false.', type: 'boolean' } @@ -155,13 +155,13 @@ for (i = 0; i < length; ++i) { } var options = { - resourceDirectory : inputDirectory, - separate : argv.separate, - separateTextures : argv.separateTextures, - secure : argv.secure, - checkTransparency : argv.checkTransparency, - stats : argv.stats, - name : outputName, + resourceDirectory: inputDirectory, + separate: argv.separate, + separateTextures: argv.separateTextures, + secure: argv.secure, + checkTransparency: argv.checkTransparency, + stats: argv.stats, + name: outputName, dracoOptions: dracoOptions }; @@ -169,7 +169,7 @@ var inputIsBinary = inputExtension === '.glb'; var outputIsBinary = outputExtension === '.glb'; var jsonOptions = { - spaces : 2 + spaces: 2 }; var read = inputIsBinary ? fsExtra.readFile : fsExtra.readJson; diff --git a/index.js b/index.js index 51bf3d13..d816c87e 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,9 @@ /*eslint-disable global-require*/ 'use strict'; module.exports = { - getStatistics : require('./lib/getStatistics'), - glbToGltf : require('./lib/glbToGltf'), - gltfToGlb : require('./lib/gltfToGlb'), - processGlb : require('./lib/processGlb'), - processGltf : require('./lib/processGltf') + getStatistics: require('./lib/getStatistics'), + glbToGltf: require('./lib/glbToGltf'), + gltfToGlb: require('./lib/gltfToGlb'), + processGlb: require('./lib/processGlb'), + processGltf: require('./lib/processGltf') }; diff --git a/lib/addDefaults.js b/lib/addDefaults.js index 07677252..77dd0bf0 100644 --- a/lib/addDefaults.js +++ b/lib/addDefaults.js @@ -148,61 +148,61 @@ function addDefaults(gltf) { } var defaultMaterial = { - values : { - emission : [ + values: { + emission: [ 0.5, 0.5, 0.5, 1.0 ] } }; var defaultTechnique = { - attributes : { - a_position : 'position' + attributes: { + a_position: 'position' }, - parameters : { - modelViewMatrix : { - semantic : 'MODELVIEW', - type : WebGLConstants.FLOAT_MAT4 + parameters: { + modelViewMatrix: { + semantic: 'MODELVIEW', + type: WebGLConstants.FLOAT_MAT4 }, - projectionMatrix : { - semantic : 'PROJECTION', - type : WebGLConstants.FLOAT_MAT4 + projectionMatrix: { + semantic: 'PROJECTION', + type: WebGLConstants.FLOAT_MAT4 }, - emission : { - type : WebGLConstants.FLOAT_VEC4, - value : [ + emission: { + type: WebGLConstants.FLOAT_VEC4, + value: [ 0.5, 0.5, 0.5, 1.0 ] }, - position : { - semantic : 'POSITION', - type : WebGLConstants.FLOAT_VEC3 + position: { + semantic: 'POSITION', + type: WebGLConstants.FLOAT_VEC3 } }, - states : { - enable : [ + states: { + enable: [ WebGLConstants.CULL_FACE, WebGLConstants.DEPTH_TEST ] }, - uniforms : { - u_modelViewMatrix : 'modelViewMatrix', - u_projectionMatrix : 'projectionMatrix', - u_emission : 'emission' + uniforms: { + u_modelViewMatrix: 'modelViewMatrix', + u_projectionMatrix: 'projectionMatrix', + u_emission: 'emission' } }; var defaultProgram = { - attributes : [ + attributes: [ 'a_position' ] }; var defaultVertexShader = { - type : WebGLConstants.VERTEX_SHADER, - extras : { - _pipeline : { - source : + type: WebGLConstants.VERTEX_SHADER, + extras: { + _pipeline: { + source: 'precision highp float;\n' + 'uniform mat4 u_modelViewMatrix;\n' + 'uniform mat4 u_projectionMatrix;\n' + @@ -216,10 +216,10 @@ var defaultVertexShader = { }; var defaultFragmentShader = { - type : WebGLConstants.FRAGMENT_SHADER, - extras : { - _pipeline : { - source : + type: WebGLConstants.FRAGMENT_SHADER, + extras: { + _pipeline: { + source: 'precision highp float;\n' + 'uniform vec4 u_emission;\n' + 'void main(void)\n' + diff --git a/lib/compressDracoMeshes.js b/lib/compressDracoMeshes.js index df55daf1..14facc9d 100644 --- a/lib/compressDracoMeshes.js +++ b/lib/compressDracoMeshes.js @@ -28,23 +28,22 @@ function getNamedAttributeData(gltf, primitive, semantic) { var packed = readAccessorPacked(gltf, accessor); return { - numberOfComponents : componentsPerAttribute, - numberOfVertices : accessor.count, - data : packed + numberOfComponents: componentsPerAttribute, + numberOfVertices: accessor.count, + data: packed }; } function addCompressionExtensionToPrimitive(gltf, primitive, attributeToId, encodedLength, encodedData) { // Remove properties from accessors. // Remove indices bufferView. - var indicesAccessor = gltf.accessors[primitive.indices]; var newIndicesAccessor = { - componentType : indicesAccessor.componentType, - count : indicesAccessor.count, - max : indicesAccessor.max, - min : indicesAccessor.min, - type : indicesAccessor.type + componentType: indicesAccessor.componentType, + count: indicesAccessor.count, + max: indicesAccessor.max, + min: indicesAccessor.min, + type: indicesAccessor.type }; var indicesAccessorId = addToArray(gltf.accessors, newIndicesAccessor); primitive.indices = indicesAccessorId; @@ -54,29 +53,29 @@ function addCompressionExtensionToPrimitive(gltf, primitive, attributeToId, enco ForEach.meshPrimitiveAttribute(primitive, function(accessorId, semantic) { var attributeAccessor = gltf.accessors[primitive.attributes[semantic]]; var newAttributeAccessor = { - componentType : attributeAccessor.componentType, - count : attributeAccessor.count, - max : attributeAccessor.max, - min : attributeAccessor.min, - type : attributeAccessor.type + componentType: attributeAccessor.componentType, + count: attributeAccessor.count, + max: attributeAccessor.max, + min: attributeAccessor.min, + type: attributeAccessor.type }; var attributeAccessorId = addToArray(gltf.accessors, newAttributeAccessor); primitive.attributes[semantic] = attributeAccessorId; }); var buffer = { - byteLength : encodedLength, - extras : { - _pipeline : { - source : encodedData + byteLength: encodedLength, + extras: { + _pipeline: { + source: encodedData } } }; var bufferId = addToArray(gltf.buffers, buffer); var bufferView = { - buffer : bufferId, - byteOffset : 0, - byteLength : encodedLength + buffer: bufferId, + byteOffset: 0, + byteLength: encodedLength }; var bufferViewId = addToArray(gltf.bufferViews, bufferView); @@ -86,8 +85,8 @@ function addCompressionExtensionToPrimitive(gltf, primitive, attributeToId, enco primitive.extensions = extensions; } var dracoExtension = { - bufferView : bufferViewId, - attributes : attributeToId + bufferView: bufferViewId, + attributes: attributeToId }; extensions.KHR_draco_mesh_compression = dracoExtension; } @@ -105,8 +104,8 @@ function copyCompressedExtensionToPrimitive(primitive, compressedPrimitive) { var extensions = {}; primitive.extensions = extensions; var copiedExtension = { - bufferView : dracoExtension.bufferView, - attributes : dracoExtension.attributes + bufferView: dracoExtension.bufferView, + attributes: dracoExtension.attributes }; extensions.KHR_draco_mesh_compression = copiedExtension; } @@ -186,9 +185,9 @@ function compressDracoMeshes(gltf, options) { } var primitiveGeometry = { - attributes : primitive.attributes, - indices : primitive.indices, - mode : primitive.mode + attributes: primitive.attributes, + indices: primitive.indices, + mode: primitive.mode }; var hashValue = hashObject(primitiveGeometry); if (defined(hashPrimitives[hashValue])) { diff --git a/lib/findAccessorMinMax.js b/lib/findAccessorMinMax.js index 0c01d5ea..f7217e77 100644 --- a/lib/findAccessorMinMax.js +++ b/lib/findAccessorMinMax.js @@ -29,8 +29,8 @@ function findAccessorMinMax(gltf, accessor) { // According to the spec, when bufferView is not defined, accessor must be initialized with zeros if (!defined(accessor.bufferView)) { return { - min : arrayFill(new Array(numberOfComponents), 0.0), - max : arrayFill(new Array(numberOfComponents), 0.0) + min: arrayFill(new Array(numberOfComponents), 0.0), + max: arrayFill(new Array(numberOfComponents), 0.0) }; } @@ -62,7 +62,7 @@ function findAccessorMinMax(gltf, accessor) { } return { - min : min, - max : max + min: min, + max: max }; } diff --git a/lib/getAccessorByteStride.js b/lib/getAccessorByteStride.js index efc635b6..dec29e14 100644 --- a/lib/getAccessorByteStride.js +++ b/lib/getAccessorByteStride.js @@ -18,9 +18,12 @@ module.exports = getAccessorByteStride; * @private */ function getAccessorByteStride(gltf, accessor) { - var bufferView = gltf.bufferViews[accessor.bufferView]; - if (defined(bufferView) && defined(bufferView.byteStride) && bufferView.byteStride > 0) { - return bufferView.byteStride; + var bufferViewId = accessor.bufferView; + if (defined(bufferViewId)) { + var bufferView = gltf.bufferViews[bufferViewId]; + if (defined(bufferView.byteStride) && bufferView.byteStride > 0) { + return bufferView.byteStride; + } } return ComponentDatatype.getSizeInBytes(accessor.componentType) * numberOfComponentsForType(accessor.type); } diff --git a/lib/getComponentReader.js b/lib/getComponentReader.js index 02eafbb0..dfa8f64d 100644 --- a/lib/getComponentReader.js +++ b/lib/getComponentReader.js @@ -8,8 +8,8 @@ module.exports = getComponentReader; /** * Returns a function to read and convert data from a DataView into an array. * - * @param {Cesium.ComponentDatatype} componentType Type to convert the data to. - * @returns {function} Function that reads and converts data. + * @param {Number} componentType Type to convert the data to. + * @returns {ComponentReader} Function that reads and converts data. * * @private */ @@ -65,3 +65,14 @@ function getComponentReader(componentType) { }; } } + +/** + * A callback function that logs messages. + * @callback ComponentReader + * + * @param {DataView} dataView The data view to read from. + * @param {Number} byteOffset The byte offset applied when reading from the data view. + * @param {Number} numberOfComponents The number of components to read. + * @param {Number} componentTypeByteLength The byte length of each component. + * @param {Number} result An array storing the components that are read. + */ diff --git a/lib/getStatistics.js b/lib/getStatistics.js index 444195ae..170ee466 100644 --- a/lib/getStatistics.js +++ b/lib/getStatistics.js @@ -111,8 +111,8 @@ function getDrawCallStatisticsForNode(gltf, nodeId) { }); return { - numberOfDrawCalls : numberOfDrawCalls, - numberOfRenderedPrimitives : numberOfRenderedPrimitives + numberOfDrawCalls: numberOfDrawCalls, + numberOfRenderedPrimitives: numberOfRenderedPrimitives }; } @@ -128,8 +128,8 @@ function getDrawCallStatistics(gltf) { }); return { - numberOfDrawCalls : numberOfDrawCalls, - numberOfRenderedPrimitives : numberOfRenderedPrimitives + numberOfDrawCalls: numberOfDrawCalls, + numberOfRenderedPrimitives: numberOfRenderedPrimitives }; } diff --git a/lib/gltfToGlb.js b/lib/gltfToGlb.js index 3581c41d..912165fb 100644 --- a/lib/gltfToGlb.js +++ b/lib/gltfToGlb.js @@ -17,14 +17,14 @@ module.exports = gltfToGlb; function gltfToGlb(gltf, options) { options = defaultValue(options, {}); options.bufferStorage = { - buffer : undefined + buffer: undefined }; return processGltf(gltf, options) .then(function(results) { return { - glb : getGlb(results.gltf, options.bufferStorage.buffer), - separateResources : results.separateResources + glb: getGlb(results.gltf, options.bufferStorage.buffer), + separateResources: results.separateResources }; }); } diff --git a/lib/mergeBuffers.js b/lib/mergeBuffers.js index aa9d5ef9..1beabde3 100644 --- a/lib/mergeBuffers.js +++ b/lib/mergeBuffers.js @@ -2,6 +2,7 @@ var Cesium = require('cesium'); var ForEach = require('./ForEach'); +var defaultValue = Cesium.defaultValue; var defined = Cesium.defined; module.exports = mergeBuffers; @@ -18,19 +19,19 @@ function mergeBuffers(gltf) { var buffersToMerge = []; var lengthSoFar = 0; + var name; + ForEach.buffer(gltf, function(buffer) { + name = defaultValue(name, buffer.name); + }); + ForEach.bufferView(gltf, function(bufferView) { if (defined(gltf.buffers[bufferView.buffer])) { var buffer = gltf.buffers[bufferView.buffer]; var sourceBufferViewData = buffer.extras._pipeline.source.slice(bufferView.byteOffset, bufferView.byteOffset + bufferView.byteLength); - // Check if new bufferView needs to be aligned to 4 bytes. - var alignment = lengthSoFar & 3; - if (alignment > 0) { - var bytesToPad = 4 - alignment; - var emptyBuffer = Buffer.alloc(bytesToPad); - buffersToMerge.push(emptyBuffer); - lengthSoFar += bytesToPad; - } + var bufferViewPadding = getPadding(lengthSoFar); + buffersToMerge.push(bufferViewPadding); + lengthSoFar += bufferViewPadding.length; bufferView.byteOffset = lengthSoFar; bufferView.buffer = 0; @@ -40,16 +41,28 @@ function mergeBuffers(gltf) { } }); + var bufferPadding = getPadding(lengthSoFar); + buffersToMerge.push(bufferPadding); var mergedSource = Buffer.concat(buffersToMerge); gltf.buffers = [{ - byteLength : mergedSource.length, - extras : { - _pipeline : { - source : mergedSource + name: name, + byteLength: mergedSource.length, + extras: { + _pipeline: { + source: mergedSource } } }]; return gltf; } + +function getPadding(length) { + var alignment = length & 3; + if (alignment > 0) { + var bytesToPad = 4 - alignment; + return Buffer.alloc(bytesToPad); + } + return Buffer.alloc(0); +} diff --git a/lib/processGltf.js b/lib/processGltf.js index 9d72ac30..e8d423cd 100644 --- a/lib/processGltf.js +++ b/lib/processGltf.js @@ -70,8 +70,8 @@ function processGltf(gltf, options) { }).then(function() { printStats(gltf, options, true); return { - gltf : gltf, - separateResources : options.separateResources + gltf: gltf, + separateResources: options.separateResources }; }); } @@ -106,31 +106,31 @@ processGltf.defaults = { * @type Boolean * @default false */ - separate : false, + separate: false, /** * Gets or sets whether to write out separate textures only. * @type Boolean * @default false */ - separateTextures : false, + separateTextures: false, /** * Gets or sets whether the converter will do a more exhaustive check for texture transparency by looking at the alpha channel of each pixel. * @type Boolean * @default false */ - checkTransparency : false, + checkTransparency: false, /** * Gets or sets whether the source model can reference paths outside of its directory. * @type Boolean * @default false */ - secure : false, + secure: false, /** * Gets or sets whether to print statistics to console for input and output glTF files. * @type Boolean * @default false */ - stats : false + stats: false }; /** diff --git a/lib/readAccessorPacked.js b/lib/readAccessorPacked.js index ff4309b3..dd771830 100644 --- a/lib/readAccessorPacked.js +++ b/lib/readAccessorPacked.js @@ -4,7 +4,9 @@ var getAccessorByteStride = require('./getAccessorByteStride'); var getComponentReader = require('./getComponentReader.js'); var numberOfComponentsForType = require('./numberOfComponentsForType'); +var arrayFill = Cesium.arrayFill; var ComponentDatatype = Cesium.ComponentDatatype; +var defined = Cesium.defined; module.exports = readAccessorPacked; @@ -18,26 +20,30 @@ module.exports = readAccessorPacked; * @private */ function readAccessorPacked(gltf, accessor) { - var bufferView = gltf.bufferViews[accessor.bufferView]; - var source = gltf.buffers[bufferView.buffer].extras._pipeline.source; - var byteOffset = accessor.byteOffset + bufferView.byteOffset + source.byteOffset; var byteStride = getAccessorByteStride(gltf, accessor); var componentTypeByteLength = ComponentDatatype.getSizeInBytes(accessor.componentType); var numberOfComponents = numberOfComponentsForType(accessor.type); var count = accessor.count; + var values = new Array(numberOfComponents * count); + + if (!defined(accessor.bufferView)) { + arrayFill(values, 0); + return values; + } + + var bufferView = gltf.bufferViews[accessor.bufferView]; + var source = gltf.buffers[bufferView.buffer].extras._pipeline.source; + var byteOffset = accessor.byteOffset + bufferView.byteOffset + source.byteOffset; var dataView = new DataView(source.buffer); var components = new Array(numberOfComponents); var componentReader = getComponentReader(accessor.componentType); - var values = []; for (var i = 0; i < count; ++i) { componentReader(dataView, byteOffset, numberOfComponents, componentTypeByteLength, components); - for (var j = 0; j < numberOfComponents; ++j) { - values.push(components[j]); + values[i * numberOfComponents + j] = components[j]; } - byteOffset += byteStride; } return values; diff --git a/lib/removeUnusedElements.js b/lib/removeUnusedElements.js index 6b6aab9d..da876e2c 100644 --- a/lib/removeUnusedElements.js +++ b/lib/removeUnusedElements.js @@ -22,12 +22,13 @@ function removeUnusedElements(gltf) { return gltf; } +var TypeToGltfElementName = { + accessor: 'accessors', + buffer: 'buffers', + bufferView: 'bufferViews' +}; + function removeUnusedElementsByType(gltf, type) { - var TypeToGltfElementName = { - accessor : 'accessors', - buffer : 'buffers', - bufferView : 'bufferViews' - }; var name = TypeToGltfElementName[type]; var arrayOfObjects = gltf[name]; @@ -125,22 +126,36 @@ Remove.bufferView = function(gltf, bufferViewId) { } }); + ForEach.shader(gltf, function(shader) { + if (defined(shader.bufferView) && shader.bufferView > bufferViewId) { + shader.bufferView--; + } + }); + ForEach.image(gltf, function(image) { if (defined(image.bufferView) && image.bufferView > bufferViewId) { image.bufferView--; } + ForEach.compressedImage(image, function(compressedImage) { + var compressedImageBufferView = compressedImage.bufferView; + if (defined(compressedImageBufferView) && compressedImageBufferView > bufferViewId) { + compressedImage.bufferView--; + } + }); }); - ForEach.mesh(gltf, function(mesh) { - ForEach.meshPrimitive(mesh, function(primitive) { - if (defined(primitive.extensions) && - defined(primitive.extensions.KHR_draco_mesh_compression)) { - if (primitive.extensions.KHR_draco_mesh_compression.bufferView > bufferViewId) { - primitive.extensions.KHR_draco_mesh_compression.bufferView--; + if (hasExtension(gltf, 'KHR_draco_mesh_compression')) { + ForEach.mesh(gltf, function (mesh) { + ForEach.meshPrimitive(mesh, function (primitive) { + if (defined(primitive.extensions) && + defined(primitive.extensions.KHR_draco_mesh_compression)) { + if (primitive.extensions.KHR_draco_mesh_compression.bufferView > bufferViewId) { + primitive.extensions.KHR_draco_mesh_compression.bufferView--; + } } - } + }); }); - }); + } }; /** @@ -215,10 +230,21 @@ getListOfElementsIdsInUse.bufferView = function(gltf) { } }); + ForEach.shader(gltf, function(shader) { + if (defined(shader.bufferView)) { + usedBufferViewIds[shader.bufferView] = true; + } + }); + ForEach.image(gltf, function(image) { if (defined(image.bufferView)) { usedBufferViewIds[image.bufferView] = true; } + ForEach.compressedImage(image, function(compressedImage) { + if (defined(compressedImage.bufferView)) { + usedBufferViewIds[compressedImage.bufferView] = true; + } + }); }); if (hasExtension(gltf, 'KHR_draco_mesh_compression')) { diff --git a/lib/updateVersion.js b/lib/updateVersion.js index b4dd91cc..5be9c4e8 100644 --- a/lib/updateVersion.js +++ b/lib/updateVersion.js @@ -1,11 +1,13 @@ 'use strict'; var Cesium = require('cesium'); +var addExtensionsUsed = require('./addExtensionsUsed'); var addExtensionsRequired = require('./addExtensionsRequired'); var addToArray = require('./addToArray'); var findAccessorMinMax = require('./findAccessorMinMax'); var ForEach = require('./ForEach'); var getAccessorByteStride = require('./getAccessorByteStride'); var numberOfComponentsForType = require('./numberOfComponentsForType'); +var removeUnusedElements = require('./removeUnusedElements'); var Cartesian3 = Cesium.Cartesian3; var CesiumMath = Cesium.Math; @@ -20,9 +22,9 @@ var WebGLConstants = Cesium.WebGLConstants; module.exports = updateVersion; var updateFunctions = { - '0.8' : glTF08to10, - '1.0' : glTF10to20, - '2.0' : undefined + '0.8': glTF08to10, + '1.0': glTF10to20, + '2.0': undefined }; /** @@ -43,7 +45,7 @@ function updateVersion(gltf, options) { var version = gltf.version; gltf.asset = defaultValue(gltf.asset, { - version : '1.0' + version: '1.0' }); version = defaultValue(version, gltf.asset.version).toString(); @@ -210,9 +212,16 @@ function glTF08to10(gltf) { var asset = gltf.asset; asset.version = '1.0'; // Profile should be an object, not a string - if (!defined(asset.profile) || (typeof asset.profile === 'string')) { + if (typeof asset.profile === 'string') { + var split = asset.profile.split(' '); + asset.profile = { + api: split[0], + version: split[1] + }; + } else { asset.profile = {}; } + // Version property should be in asset, not on the root element if (defined(gltf.version)) { delete gltf.version; @@ -221,13 +230,18 @@ function glTF08to10(gltf) { updateInstanceTechniques(gltf); // primitive.primitive should be primitive.mode setPrimitiveModes(gltf); - // node rotation should be quaternion, not axis-angle + // Node rotation should be quaternion, not axis-angle // node.instanceSkin is deprecated updateNodes(gltf); - // animations that target rotations should be quaternion, not axis-angle + // Animations that target rotations should be quaternion, not axis-angle updateAnimations(gltf); // technique.pass and techniques.passes are deprecated removeTechniquePasses(gltf); + // gltf.allExtensions -> extensionsUsed + if (defined(gltf.allExtensions)) { + gltf.extensionsUsed = gltf.allExtensions; + delete gltf.allExtensions; + } // gltf.lights -> khrMaterialsCommon.lights if (defined(gltf.lights)) { var extensions = defaultValue(gltf.extensions, {}); @@ -236,11 +250,7 @@ function glTF08to10(gltf) { extensions.KHR_materials_common = materialsCommon; materialsCommon.lights = gltf.lights; delete gltf.lights; - } - // gltf.allExtensions -> extensionsUsed - if (defined(gltf.allExtensions)) { - gltf.extensionsUsed = gltf.allExtensions; - delete gltf.allExtensions; + addExtensionsUsed(gltf, 'KHR_materials_common'); } } @@ -283,22 +293,22 @@ function objectToArray(object, mapping) { function objectsToArrays(gltf) { var i; var globalMapping = { - accessors : {}, - animations : {}, - buffers : {}, - bufferViews : {}, - cameras : {}, - images : {}, - materials : {}, - meshes : {}, - nodes : {}, - programs : {}, - samplers : {}, - scenes : {}, - shaders : {}, - skins : {}, - textures : {}, - techniques : {} + accessors: {}, + animations: {}, + buffers: {}, + bufferViews: {}, + cameras: {}, + images: {}, + materials: {}, + meshes: {}, + nodes: {}, + programs: {}, + samplers: {}, + scenes: {}, + shaders: {}, + skins: {}, + textures: {}, + techniques: {} }; // Map joint names to id names @@ -377,7 +387,7 @@ function objectsToArrays(gltf) { var value = parameter.value; if (typeof value === 'string') { parameter.value = { - index : globalMapping.textures[value] + index: globalMapping.textures[value] }; } }); @@ -411,7 +421,7 @@ function objectsToArrays(gltf) { node.mesh = globalMapping.meshes[meshes[0]]; for (i = 1; i < meshesLength; ++i) { var meshNode = { - mesh : globalMapping.meshes[meshes[i]] + mesh: globalMapping.meshes[meshes[i]] }; var meshNodeId = addToArray(gltf.nodes, meshNode); if (!defined(children)) { @@ -490,7 +500,7 @@ function objectsToArrays(gltf) { ForEach.materialValue(material, function(value, name) { if (typeof value === 'string') { material.values[name] = { - index : globalMapping.textures[value] + index: globalMapping.textures[value] }; } }); @@ -501,7 +511,7 @@ function objectsToArrays(gltf) { ForEach.materialValue(materialsCommon, function(value, name) { if (typeof value === 'string') { materialsCommon.values[name] = { - index : globalMapping.textures[value] + index: globalMapping.textures[value] }; } }); @@ -563,16 +573,15 @@ function removeEmptyArrays(gltf) { } function stripAsset(gltf) { - // asset.premultipliedAlpha is removed in glTF 2.0 however it is important - // to preserve for assets using the KHR_technique_webgl extension var asset = gltf.asset; delete asset.profile; + delete asset.premultipliedAlpha; } var knownExtensions = { - CESIUM_RTC : true, - KHR_materials_common : true, - WEB3D_quantized_attributes : true + CESIUM_RTC: true, + KHR_materials_common: true, + WEB3D_quantized_attributes: true }; function requireKnownExtensions(gltf) { var extensionsUsed = gltf.extensionsUsed; @@ -632,13 +641,13 @@ function requireAttributeSetIndex(gltf) { } var knownSemantics = { - POSITION : true, - NORMAL : true, - TANGENT : true, - TEXCOORD : true, - COLOR : true, - JOINT : true, - WEIGHTS : true + POSITION: true, + NORMAL: true, + TANGENT: true, + TEXCOORD: true, + COLOR: true, + JOINT: true, + WEIGHTS: true }; function underscoreApplicationSpecificSemantics(gltf) { var mappedSemantics = {}; @@ -749,10 +758,12 @@ function requireByteLength(gltf) { }); ForEach.accessor(gltf, function(accessor) { var bufferViewId = accessor.bufferView; - var bufferView = gltf.bufferViews[bufferViewId]; - var accessorByteStride = computeAccessorByteStride(gltf, accessor); - var accessorByteEnd = accessor.byteOffset + accessor.count * accessorByteStride; - bufferView.byteLength = Math.max(defaultValue(bufferView.byteLength, 0), accessorByteEnd); + if (defined(bufferViewId)) { + var bufferView = gltf.bufferViews[bufferViewId]; + var accessorByteStride = computeAccessorByteStride(gltf, accessor); + var accessorByteEnd = accessor.byteOffset + accessor.count * accessorByteStride; + bufferView.byteLength = Math.max(defaultValue(bufferView.byteLength, 0), accessorByteEnd); + } }); } @@ -762,28 +773,21 @@ function moveByteStrideToBufferView(gltf) { var bufferView; var bufferViews = gltf.bufferViews; - // Determine whether each buffer view stores vertex attributes or not var bufferViewHasVertexAttributes = {}; - ForEach.mesh(gltf, function(mesh) { - ForEach.meshPrimitive(mesh, function (primitive) { - ForEach.meshPrimitiveAttribute(primitive, function(accessorId) { - var accessor = gltf.accessors[accessorId]; - bufferViewHasVertexAttributes[accessor.bufferView] = true; - }); - ForEach.meshPrimitiveTarget(primitive, function(target) { - ForEach.meshPrimitiveTargetAttribute(target, function(accessorId) { - var accessor = gltf.accessors[accessorId]; - bufferViewHasVertexAttributes[accessor.bufferView] = true; - }); - }); - }); + ForEach.accessorContainingVertexAttributeData(gltf, function(accessorId) { + var accessor = gltf.accessors[accessorId]; + if (defined(accessor.bufferView)) { + bufferViewHasVertexAttributes[accessor.bufferView] = true; + } }); // Map buffer views to a list of accessors var bufferViewMap = {}; ForEach.accessor(gltf, function(accessor) { - bufferViewMap[accessor.bufferView] = defaultValue(bufferViewMap[accessor.bufferView], []); - bufferViewMap[accessor.bufferView].push(accessor); + if (defined(accessor.bufferView)) { + bufferViewMap[accessor.bufferView] = defaultValue(bufferViewMap[accessor.bufferView], []); + bufferViewMap[accessor.bufferView].push(accessor); + } }); // Split accessors with different byte strides @@ -794,7 +798,6 @@ function moveByteStrideToBufferView(gltf) { accessors.sort(function(a, b) { return a.byteOffset - b.byteOffset; }); - var currentByteOffset = 0; var currentIndex = 0; var accessorsLength = accessors.length; @@ -826,51 +829,8 @@ function moveByteStrideToBufferView(gltf) { } } - // Delete old buffer views and build the buffer view shift map. - var removeCount = 0; - var bufferViewShiftMap = {}; - var bufferViewsLength = bufferViews.length; - for (i = 0; i < bufferViewsLength; ++i) { - bufferView = bufferViews[i]; - if (defined(bufferViewMap[i])) { - ++removeCount; - continue; - } - if (removeCount > 0) { - var shiftIndex = i - removeCount; - bufferViews[shiftIndex] = bufferView; - bufferViewShiftMap[i] = shiftIndex; - } - } - bufferViews.length -= removeCount; - - // Update bufferView property for all objects that reference buffer views - ForEach.accessor(gltf, function(accessor) { - var accessorBufferView = accessor.bufferView; - if (defined(accessorBufferView)) { - accessor.bufferView = bufferViewShiftMap[accessorBufferView]; - } - }); - - ForEach.shader(gltf, function(shader) { - var shaderBufferView = shader.bufferView; - if (defined(shaderBufferView)) { - shader.bufferView = bufferViewShiftMap[shaderBufferView]; - } - }); - - ForEach.image(gltf, function(image) { - var imageBufferView = image.bufferView; - if (defined(imageBufferView)) { - image.bufferView = bufferViewShiftMap[imageBufferView]; - } - ForEach.compressedImage(image, function(compressedImage) { - var compressedImageBufferView = compressedImage.bufferView; - if (defined(compressedImageBufferView)) { - compressedImage.bufferView = bufferViewShiftMap[compressedImageBufferView]; - } - }); - }); + // Remove unused buffer views + removeUnusedElements(gltf); } function requireAccessorMinMax(gltf) { @@ -915,19 +875,18 @@ function addKHRTechniqueExtension(gltf) { } function glTF10to20(gltf) { + // TODO : KHR_techniques_webgl gltf.asset = defaultValue(gltf.asset, {}); gltf.asset.version = '2.0'; // material.instanceTechnique properties should be directly on the material. instanceTechnique is a gltf 0.8 property but is seen in some 1.0 models. updateInstanceTechniques(gltf); // animation.samplers now refers directly to accessors and animation.parameters should be removed removeAnimationSamplersIndirection(gltf); - // top-level objects are now arrays referenced by index instead of id + // Top-level objects are now arrays referenced by index instead of id objectsToArrays(gltf); - // remove empty arrays - removeEmptyArrays(gltf); // asset.profile no longer exists stripAsset(gltf); - // move known extensions from extensionsUsed to extensionsRequired + // Move known extensions from extensionsUsed to extensionsRequired requireKnownExtensions(gltf); // bufferView.byteLength and buffer.byteLength are required requireByteLength(gltf); @@ -937,22 +896,24 @@ function glTF10to20(gltf) { requireAccessorMinMax(gltf); // buffer.type is unnecessary and should be removed removeBufferType(gltf); - // remove format, internalFormat, target, and type + // Remove format, internalFormat, target, and type removeTextureProperties(gltf); // TEXCOORD and COLOR attributes must be written with a set index (TEXCOORD_#) requireAttributeSetIndex(gltf); // Add underscores to application-specific parameters underscoreApplicationSpecificSemantics(gltf); - // remove scissor from techniques + // Remove scissor from techniques removeScissorFromTechniques(gltf); - // clamp technique function states to min/max + // Clamp technique function states to min/max clampTechniqueFunctionStates(gltf); - // clamp camera parameters + // Clamp camera parameters clampCameraParameters(gltf); - // a technique parameter specified as an attribute cannot have a value + // A technique parameter specified as an attribute cannot have a value stripTechniqueAttributeValues(gltf); - // only techniques with a JOINTMATRIX or application specific semantic may have a defined count property + // Only techniques with a JOINTMATRIX or application specific semantic may have a defined count property stripTechniqueParameterCount(gltf); - // add KHR_technique_webgl extension + // Add KHR_technique_webgl extension addKHRTechniqueExtension(gltf); + // Remove empty arrays + removeEmptyArrays(gltf); } diff --git a/lib/writeResources.js b/lib/writeResources.js index 05a5c46f..31a77e8c 100644 --- a/lib/writeResources.js +++ b/lib/writeResources.js @@ -61,8 +61,8 @@ function writeResources(gltf, options) { // Buffers need to be written last because images and shaders may write to new buffers return Promise.all(promises) .then(function() { - mergeBuffers(gltf); removeUnusedElements(gltf); + mergeBuffers(gltf); return writeBuffer(gltf, gltf.buffers[0], 0, options); }) .then(function() { @@ -128,15 +128,15 @@ function writeBufferView(gltf, object) { object.bufferView = bufferViews.length; bufferViews.push({ - buffer : buffers.length, - byteOffset : 0, - byteLength : byteLength + buffer: buffers.length, + byteOffset: 0, + byteLength: byteLength }); buffers.push({ - byteLength : byteLength, - extras : { - _pipeline : { - source : source + byteLength: byteLength, + extras: { + _pipeline: { + source: source } } }); @@ -146,8 +146,8 @@ function getProgram(gltf, shaderIndex) { ForEach.program(gltf, function(program, index) { if (program.fragmentShader === shaderIndex || program.vertexShader === shaderIndex) { return { - program : program, - index : index + program: program, + index: index }; } }); diff --git a/specs/data/1.0/box-textured-separate/box-textured-separate.gltf b/specs/data/1.0/box-textured-separate/box-textured-separate.gltf index c33a38c3..11eb66fc 100644 --- a/specs/data/1.0/box-textured-separate/box-textured-separate.gltf +++ b/specs/data/1.0/box-textured-separate/box-textured-separate.gltf @@ -94,7 +94,6 @@ }, "images": { "Image0001": { - "name": "Image0001", "uri": "cesium.png" } }, diff --git a/specs/lib/addDefaultsSpec.js b/specs/lib/addDefaultsSpec.js index aab9f36e..81686106 100644 --- a/specs/lib/addDefaultsSpec.js +++ b/specs/lib/addDefaultsSpec.js @@ -7,68 +7,68 @@ var WebGLConstants = Cesium.WebGLConstants; describe('addDefaults', function() { it('adds mesh, accessor, and bufferView defaults', function() { var gltf = { - meshes : [ + meshes: [ { - primitives : [ + primitives: [ { - attributes : { - POSITION : 0 + attributes: { + POSITION: 0 }, - indices : 2, - targets : [ + indices: 2, + targets: [ { - POSITION : 1 + POSITION: 1 } ] } ] } ], - accessors : [ + accessors: [ { - bufferView : 0, - componentType : WebGLConstants.FLOAT, - count : 24, - type : 'VEC3', - min : [-1.0, -1.0, -1.0], - max : [1.0, 1.0, 1.0] + bufferView: 0, + componentType: WebGLConstants.FLOAT, + count: 24, + type: 'VEC3', + min: [-1.0, -1.0, -1.0], + max: [1.0, 1.0, 1.0] }, { - bufferView : 1, - componentType : WebGLConstants.FLOAT, - count : 24, - type : 'VEC3', - min : [-1.0, -1.0, -1.0], - max : [1.0, 1.0, 1.0] + bufferView: 1, + componentType: WebGLConstants.FLOAT, + count: 24, + type: 'VEC3', + min: [-1.0, -1.0, -1.0], + max: [1.0, 1.0, 1.0] }, { - bufferView : 2, - componentType : WebGLConstants.UNSIGNED_SHORT, - count : 36, - type : 'SCALAR', - min : [0], - max : [24] + bufferView: 2, + componentType: WebGLConstants.UNSIGNED_SHORT, + count: 36, + type: 'SCALAR', + min: [0], + max: [24] } ], - bufferViews : [ + bufferViews: [ { - buffer : 0, - byteLength : 288 + buffer: 0, + byteLength: 288 }, { - buffer : 0, - byteLength : 288, - byteOffset : 288 + buffer: 0, + byteLength: 288, + byteOffset: 288 }, { - buffer : 0, - byteLength : 72, - byteOffset : 576 + buffer: 0, + byteLength: 72, + byteOffset: 576 }, { - buffer : 0, - byteLength : 10, - byteOffset : 648 + buffer: 0, + byteLength: 10, + byteOffset: 648 } ] }; @@ -105,20 +105,20 @@ describe('addDefaults', function() { it('adds material defaults', function() { var gltf = { - materials : [ + materials: [ { - emissiveTexture : { - index : 0 + emissiveTexture: { + index: 0 }, - normalTexture : { - index : 1 + normalTexture: { + index: 1 }, - occlusionTexture : { - index : 2 + occlusionTexture: { + index: 2 } }, { - alphaMode : 'MASK' + alphaMode: 'MASK' } ] }; @@ -139,14 +139,14 @@ describe('addDefaults', function() { it('adds metallic roughness defaults', function() { var gltf = { - materials : [ + materials: [ { - pbrMetallicRoughness : { - baseColorTexture : { - index : 0 + pbrMetallicRoughness: { + baseColorTexture: { + index: 0 }, - metallicRoughnessTexture : { - index : 1 + metallicRoughnessTexture: { + index: 1 } } } @@ -164,12 +164,12 @@ describe('addDefaults', function() { it('adds spec gloss defaults', function() { var gltf = { - materials : [ + materials: [ { - extensions : { - pbrSpecularGlossiness : { - specularGlossinessTexture : { - index : 0 + extensions: { + pbrSpecularGlossiness: { + specularGlossinessTexture: { + index: 0 } } } @@ -187,25 +187,25 @@ describe('addDefaults', function() { it('adds materials common defaults', function() { var gltf = { - materials : [ + materials: [ { - extensions : { - KHR_materials_common : { - technique : 'BLINN' + extensions: { + KHR_materials_common: { + technique: 'BLINN' } } }, { - extensions : { - KHR_materials_common : { - technique : 'CONSTANT' + extensions: { + KHR_materials_common: { + technique: 'CONSTANT' } } }, { - extensions : { - KHR_materials_common : { - technique : 'LAMBERT' + extensions: { + KHR_materials_common: { + technique: 'LAMBERT' } } } @@ -236,7 +236,7 @@ describe('addDefaults', function() { it('adds sampler defaults', function() { var gltf = { - samplers : [ + samplers: [ { // Intentionally empty } @@ -251,35 +251,35 @@ describe('addDefaults', function() { it('adds node defaults', function() { var gltf = { - animations : [ + animations: [ { - channels : [ + channels: [ { - sampler : 0, - target : { - node : 0, - path : 'rotation' + sampler: 0, + target: { + node: 0, + path: 'rotation' } } ], - samplers : [ + samplers: [ { - input : 0, - output : 1 + input: 0, + output: 1 } ] } ], - nodes : [ + nodes: [ { - mesh : 0 + mesh: 0 }, { - mesh : 1 + mesh: 1 }, { - mesh : 2, - translation : [1.0, 0.0, 0.0] + mesh: 2, + translation: [1.0, 0.0, 0.0] } ] }; @@ -309,9 +309,9 @@ describe('addDefaults', function() { it('adds scene defaults', function() { var gltf = { - scenes : [ + scenes: [ { - nodes : [ + nodes: [ 0 ] } diff --git a/specs/lib/addPipelineExtrasSpec.js b/specs/lib/addPipelineExtrasSpec.js index f339cee6..f59856ca 100644 --- a/specs/lib/addPipelineExtrasSpec.js +++ b/specs/lib/addPipelineExtrasSpec.js @@ -6,28 +6,28 @@ var WebGLConstants = Cesium.WebGLConstants; describe('addPipelineExtras', function() { it('adds pipeline extras', function() { - // TODO KHR_technique_webgl: remove shaders from top level, put inside extras.KHR_technique_webgl + // TODO KHR_techniques_webgl: remove shaders from top level, put inside extras.KHR_techniques_webgl var gltf = { - buffers : [ + buffers: [ { - byteLength : 100 + byteLength: 100 } ], - shaders : [ + shaders: [ { - type : WebGLConstants.VERTEX_SHADER, + type: WebGLConstants.VERTEX_SHADER, uri: 'data:,' } ], - images : [ + images: [ { - extras : { - compressedImage3DTiles : { - s3tc : { - uri : 'data:,' + extras: { + compressedImage3DTiles: { + s3tc: { + uri: 'data:,' }, - etc1 : { - uri : 'data:,' + etc1: { + uri: 'data:,' } } } diff --git a/specs/lib/addToArraySpec.js b/specs/lib/addToArraySpec.js index c9fee21a..f684bfc9 100644 --- a/specs/lib/addToArraySpec.js +++ b/specs/lib/addToArraySpec.js @@ -4,13 +4,13 @@ var addToArray = require('../../lib/addToArray'); describe('addToArray', function() { it('adds item to array and returns its index', function() { var gltf = { - buffers : [] + buffers: [] }; var buffer0 = { - byteLength : 100 + byteLength: 100 }; var buffer1 = { - byteLength : 200 + byteLength: 200 }; expect(addToArray(gltf.buffers, buffer0)).toBe(0); expect(addToArray(gltf.buffers, buffer1)).toBe(1); diff --git a/specs/lib/compressDracoMeshesSpec.js b/specs/lib/compressDracoMeshesSpec.js new file mode 100644 index 00000000..0186dc12 --- /dev/null +++ b/specs/lib/compressDracoMeshesSpec.js @@ -0,0 +1,7 @@ +'use strict'; +var compressDracoMeshes = require('../../lib/compressDracoMeshes'); + +describe('compressDracoMeshes', function() { + it('compressDracoMeshes', function() { + }); +}); diff --git a/specs/lib/findAccessorMinMaxSpec.js b/specs/lib/findAccessorMinMaxSpec.js index 8c550e11..3c657585 100644 --- a/specs/lib/findAccessorMinMaxSpec.js +++ b/specs/lib/findAccessorMinMaxSpec.js @@ -26,37 +26,36 @@ function createGltf(elements, byteStride) { var byteLength = buffer.length; var dataUri = 'data:application/octet-stream;base64,' + buffer.toString('base64'); var gltf = { - asset : { - version : '2.0' + asset: { + version: '2.0' }, - accessors : [ + accessors: [ { - bufferView : 0, - byteOffset : 0, - componentType : 5126, - count : 4, - type : 'VEC3' + bufferView: 0, + byteOffset: 0, + componentType: 5126, + count: 4, + type: 'VEC3' } ], - bufferViews : [ + bufferViews: [ { - buffer : 0, - byteOffset : 0, - byteLength : byteLength, - byteStride : byteStride + buffer: 0, + byteOffset: 0, + byteLength: byteLength, + byteStride: byteStride } ], - buffers : [ + buffers: [ { - uri : dataUri, - byteLength : byteLength + uri: dataUri, + byteLength: byteLength } ] }; return readResources(gltf); } -// TODO : test more component types describe('findAccessorMinMax', function() { it('finds the min and max of an accessor', function(done) { expect(createGltf(contiguousData, 12) diff --git a/specs/lib/getAccessorByteStrideSpec.js b/specs/lib/getAccessorByteStrideSpec.js index 7c084afb..462172eb 100644 --- a/specs/lib/getAccessorByteStrideSpec.js +++ b/specs/lib/getAccessorByteStrideSpec.js @@ -7,56 +7,56 @@ var WebGLConstants = Cesium.WebGLConstants; describe('getAccessorByteStride', function() { it('gets accessor byte stride', function() { var gltf = { - accessors : [ + accessors: [ { - componentType : WebGLConstants.FLOAT, - count : 24, - type : 'VEC3', - min : [-1.0, -1.0, -1.0], - max : [1.0, 1.0, 1.0] + componentType: WebGLConstants.FLOAT, + count: 24, + type: 'VEC3', + min: [-1.0, -1.0, -1.0], + max: [1.0, 1.0, 1.0] }, { - bufferView : 0, - componentType : WebGLConstants.FLOAT, - count : 24, - type : 'VEC3', - min : [-1.0, -1.0, -1.0], - max : [1.0, 1.0, 1.0] + bufferView: 0, + componentType: WebGLConstants.FLOAT, + count: 24, + type: 'VEC3', + min: [-1.0, -1.0, -1.0], + max: [1.0, 1.0, 1.0] }, { - bufferView : 1, - componentType : WebGLConstants.FLOAT, - count : 24, - type : 'VEC3', - min : [-1.0, -1.0, -1.0], - max : [1.0, 1.0, 1.0] + bufferView: 1, + componentType: WebGLConstants.FLOAT, + count: 24, + type: 'VEC3', + min: [-1.0, -1.0, -1.0], + max: [1.0, 1.0, 1.0] }, { - componentType : WebGLConstants.FLOAT, - count : 24, - type : 'VEC2', - min : [0.0, 0.0], - max : [1.0, 1.0] + componentType: WebGLConstants.FLOAT, + count: 24, + type: 'VEC2', + min: [0.0, 0.0], + max: [1.0, 1.0] }, { - componentType : WebGLConstants.INT, - count : 36, - type : 'SCALAR', - min : [0], - max : [24] + componentType: WebGLConstants.INT, + count: 36, + type: 'SCALAR', + min: [0], + max: [24] } ], - bufferViews : [ + bufferViews: [ { - buffer : 0, - byteLength : 288, - byteOffset : 0 + buffer: 0, + byteLength: 288, + byteOffset: 0 }, { - buffer : 0, - byteLength : 288, - byteOffset : 288, - byteStride : 32 + buffer: 0, + byteLength: 288, + byteOffset: 288, + byteStride: 32 } ] }; diff --git a/specs/lib/getComponentReaderSpec.js b/specs/lib/getComponentReaderSpec.js new file mode 100644 index 00000000..4fd887b0 --- /dev/null +++ b/specs/lib/getComponentReaderSpec.js @@ -0,0 +1,30 @@ +'use strict'; +var Cesium = require('cesium'); +var getComponentReader = require('../../lib/getComponentReader'); + +var ComponentDatatype = Cesium.ComponentDatatype; + +function testComponentReader(componentType) { + var typedArray = ComponentDatatype.createTypedArray(componentType, [0, 1, 2]); + var dataView = new DataView(typedArray.buffer); + var componentTypeByteLength = ComponentDatatype.getSizeInBytes(componentType); + var componentReader = getComponentReader(componentType); + var byteOffset = componentTypeByteLength; + var numberOfComponents = 2; + var result = new Array(numberOfComponents); + componentReader(dataView, byteOffset, numberOfComponents, componentTypeByteLength, result); + expect(result).toEqual([1, 2]); +} + +describe('getComponentReader', function() { + it('reads values', function() { + testComponentReader(ComponentDatatype.BYTE); + testComponentReader(ComponentDatatype.UNSIGNED_BYTE); + testComponentReader(ComponentDatatype.SHORT); + testComponentReader(ComponentDatatype.UNSIGNED_SHORT); + testComponentReader(ComponentDatatype.INT); + testComponentReader(ComponentDatatype.UNSIGNED_INT); + testComponentReader(ComponentDatatype.FLOAT); + testComponentReader(ComponentDatatype.DOUBLE); + }); +}); diff --git a/specs/lib/getImageExtensionSpec.js b/specs/lib/getImageExtensionSpec.js index c0c11573..13763265 100644 --- a/specs/lib/getImageExtensionSpec.js +++ b/specs/lib/getImageExtensionSpec.js @@ -2,12 +2,12 @@ var dataUriToBuffer = require('../../lib/dataUriToBuffer'); var getImageExtension = require('../../lib/getImageExtension'); -var pngData = dataUriToBuffer('data:image/png;base64,'); -var gifData = dataUriToBuffer('data:image/gif;base64,'); -var jpgData = dataUriToBuffer('data:image/jpeg;base64,'); -var bmpData = dataUriToBuffer('data:image/bmp;base64,'); -var ktxData = dataUriToBuffer('data:image/ktx:base64,'); -var crnData = dataUriToBuffer('data:image/crn:base64,'); +var pngData = dataUriToBuffer('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAIAAAAmkwkpAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gQcDxwOcoRpqQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAFElEQVQI12P8//8/AwwwMSAB3BwAlm4DBdoYksUAAAAASUVORK5CYII='); +var gifData = dataUriToBuffer('data:image/gif;base64,R0lGODdhBAAEAIAAAP///////ywAAAAABAAEAAACBISPCQUAOw=='); +var jpgData = dataUriToBuffer('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wgARCAAEAAQDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACP/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAVSf/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABBQJ//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPwF//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAgEBPwF//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQAGPwJ//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABPyF//9oADAMBAAIAAwAAABCf/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPxB//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAgEBPxB//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABPxB//9k='); +var bmpData = dataUriToBuffer('data:image/bmp;base64,Qk1mAAAAAAAAADYAAAAoAAAABAAAAAQAAAABABgAAAAAADAAAAATCwAAEwsAAAAAAAAAAAAA////////////////////////////////////////////////////////////////'); +var ktxData = dataUriToBuffer('data:image/ktx:base64,q0tUWCAxMbsNChoKAQIDBAEUAAABAAAACBkAAFiAAAAIGQAABAAAAAQAAAAAAAAAAAAAAAEAAAABAAAAIAAAABsAAABLVFhPcmllbnRhdGlvbgBTPXIsVD1kLFI9aQAAQAAAAP////////////////////////////////////////////////////////////////////////////////////8='); +var crnData = dataUriToBuffer('data:image/crn:base64,SHgAUktGAAAAlds6AAQABAMBAAAAAAAAAAAAAAAAAAAAAABSAAAXAAEAAGkAAAwAAQAAAAAAAAAAAAAAAAAAAAAAHQAAdQAAAJIAAACTAAAAlACCYIAAAAAAABkwBAmCAAAAAAAAbRYAAGZggAAAAAAAGNAAAAZgAAAAAAAAEAAzAAAAAAAAAIABmAAAAAAAAAQAAAA='); var textData = dataUriToBuffer('data:text/plain;charset=utf-8,randomtext'); describe('getImageExtension', function() { diff --git a/specs/lib/getJsonBufferPaddedSpec.js b/specs/lib/getJsonBufferPaddedSpec.js index 69400a29..b0ac2fd8 100644 --- a/specs/lib/getJsonBufferPaddedSpec.js +++ b/specs/lib/getJsonBufferPaddedSpec.js @@ -4,17 +4,14 @@ var getJsonBufferPadded = require('../../lib/getJsonBufferPadded'); describe('getJsonBufferPadded', function() { it('get json buffer padded to 4 bytes', function() { var gltf = { - asset : { - version : '2.0' + asset: { + version: '2.0' } }; var string = JSON.stringify(gltf); - console.log(string.length); - // TODO : remove above - + expect(string.length).toBe(27); var bufferPadded = getJsonBufferPadded(gltf); - expect(bufferPadded.length).toBe(100); - // TODO : read char function? - expect(bufferPadded.readUInt8(99)).toBe(32); // Space + expect(bufferPadded.length).toBe(28); + expect(bufferPadded.readUInt8(27)).toBe(32); // Space }); }); diff --git a/specs/lib/getStatisticsSpec.js b/specs/lib/getStatisticsSpec.js index e83ff5a2..7f1565b3 100644 --- a/specs/lib/getStatisticsSpec.js +++ b/specs/lib/getStatisticsSpec.js @@ -6,28 +6,44 @@ describe('getStatistics', function() { var gltf = { accessors: [ { + componentType: 5123, + type: 'SCALAR', count: 1 - }, { + }, + { + componentType: 5123, + type: 'SCALAR', count: 2 - }, { + }, + { + componentType: 5123, + type: 'SCALAR', + count: 6 + }, + { + componentType: 5126, + type: 'VEC3', count: 6 } ], buffers: [ { byteLength: 140 - }, { + }, + { byteLength: 120, - uri: 'external_buffer' + uri: 'buffer.bin' } ], images: [ { - uri: 'external_image' - }, { + uri: 'image.png' + }, + { uri: 'data:image/png;' - }, { - uri: 'another_external_image' + }, + { + uri: 'image2.png' } ], meshes: [ @@ -36,12 +52,19 @@ describe('getStatistics', function() { { indices: 0, mode: 0 // POINTS - }, { + }, + { indices: 1, mode: 1 // LINES + }, + { + attributes: { + POSITION: 3 + } } ] - }, { + }, + { primitives: [ { indices: 2, @@ -56,23 +79,16 @@ describe('getStatistics', function() { animations: [ {}, {} ,{} ], - shaders: [ - { - uri: 'external_shader' - }, { - uri: 'data:text/plain;' - } - ], nodes: [ {} ] }; var statistics = getStatistics(gltf); - expect(statistics.buffersSizeInBytes).toEqual(260); + expect(statistics.buffersByteLength).toEqual(260); expect(statistics.numberOfImages).toEqual(3); - expect(statistics.numberOfExternalRequests).toEqual(4); - expect(statistics.numberOfDrawCalls).toEqual(3); - expect(statistics.numberOfRenderedPrimitives).toEqual(4); + expect(statistics.numberOfExternalRequests).toEqual(3); + expect(statistics.numberOfDrawCalls).toEqual(4); + expect(statistics.numberOfRenderedPrimitives).toEqual(6); expect(statistics.numberOfNodes).toEqual(1); expect(statistics.numberOfMeshes).toEqual(2); expect(statistics.numberOfMaterials).toEqual(2); diff --git a/specs/lib/glbToGltfSpec.js b/specs/lib/glbToGltfSpec.js index 0aea1fc2..81ccdd8d 100644 --- a/specs/lib/glbToGltfSpec.js +++ b/specs/lib/glbToGltfSpec.js @@ -1,7 +1,23 @@ 'use strict'; +var fsExtra = require('fs-extra'); var glbToGltf = require('../../lib/glbToGltf'); +var glbPath = 'specs/data/2.0/box-textured-binary/box-textured-binary.glb'; + describe('glbToGltf', function() { - it('glbToGltf', function() { + it('glbToGltf', function(done) { + spyOn(console, 'log'); + var glb = fsExtra.readFileSync(glbPath); + var options = { + separate: true, + stats: true + }; + expect(glbToGltf(glb, options) + .then(function(results) { + expect(results.gltf).toBeDefined(); + expect(results.separateResources).toBeDefined(); + expect(results.gltf.buffers.length).toBe(1); + expect(console.log).toHaveBeenCalled(); + }), done).toResolve(); }); }); diff --git a/specs/lib/gltfToGlbSpec.js b/specs/lib/gltfToGlbSpec.js index 6b813867..5915b2d4 100644 --- a/specs/lib/gltfToGlbSpec.js +++ b/specs/lib/gltfToGlbSpec.js @@ -1,7 +1,22 @@ 'use strict'; +var fsExtra = require('fs-extra'); var gltfToGlb = require('../../lib/gltfToGlb'); +var gltfPath = 'specs/data/2.0/box-textured-embedded/box-textured-embedded.gltf'; + describe('gltfToGlb', function() { - it('gltfToGlb', function() { + it('gltfToGlb', function(done) { + spyOn(console, 'log'); + var gltf = fsExtra.readJsonSync(gltfPath); + var options = { + separate: true, + stats: true + }; + expect(gltfToGlb(gltf, options) + .then(function(results) { + expect(Buffer.isBuffer(results.glb)).toBe(true); + expect(results.separateResources).toBeDefined(); + expect(console.log).toHaveBeenCalled(); + }), done).toResolve(); }); }); diff --git a/specs/lib/hasExtensionSpec.js b/specs/lib/hasExtensionSpec.js index dc69789d..34db2401 100644 --- a/specs/lib/hasExtensionSpec.js +++ b/specs/lib/hasExtensionSpec.js @@ -2,6 +2,18 @@ var hasExtension = require('../../lib/hasExtension'); describe('hasExtension', function() { - it('hasExtension', function() { + it('has extension', function() { + var gltf = { + extensionsUsed : [ + 'extension1', + 'extension2' + ] + }; + expect(hasExtension(gltf, 'extension1')).toBe(true); + expect(hasExtension(gltf, 'extension2')).toBe(true); + expect(hasExtension(gltf, 'extension3')).toBe(false); + + var emptyGltf = {}; + expect(hasExtension(emptyGltf, 'extension1')).toBe(false); }); }); diff --git a/specs/lib/mergeBuffersSpec.js b/specs/lib/mergeBuffersSpec.js index 7ba7e5b5..373f8eb9 100644 --- a/specs/lib/mergeBuffersSpec.js +++ b/specs/lib/mergeBuffersSpec.js @@ -1,7 +1,58 @@ 'use strict'; var mergeBuffers = require('../../lib/mergeBuffers'); +var readResources = require('../../lib/readResources'); describe('mergeBuffers', function() { - it('mergeBuffers', function() { + it('merges buffers', function(done) { + var nan = Number.NaN; + var buffer0 = Buffer.from((new Uint8Array([1, 1, 1, 2, 2, nan, 3, 3, 3]))); + var buffer1 = Buffer.from((new Uint8Array([4, 4, 4, 4, 4, nan, nan, nan, nan, nan]))); + var dataUri0 = 'data:application/octet-stream;base64,' + buffer0.toString('base64'); + var dataUri1 = 'data:application/octet-stream;base64,' + buffer1.toString('base64'); + + // All buffer views start on 4-byte alignment, the buffer ends on a 4-byte alignment, and extraneous buffer data is removed + var expectedBuffer = Buffer.from((new Uint8Array([1, 1, 1, 0, 2, 2, 0, 0, 3, 3, 3, 0, 4, 4, 4, 4, 4, 0, 0, 0]))); + + var gltf = { + bufferViews: [ + { + buffer: 0, + byteOffset: 0, + byteLength: 3 + }, + { + buffer: 0, + byteOffset: 3, + byteLength: 2 + }, + { + buffer: 0, + byteOffset: 6, + byteLength: 3 + }, + { + buffer: 1, + byteOffset: 0, + byteLength: 5 + } + ], + buffers: [ + { + byteLength: buffer0.length, + uri: dataUri0 + }, + { + byteLength: buffer1.length, + uri: dataUri1 + } + ] + }; + + expect(readResources(gltf) + .then(function(gltf) { + mergeBuffers(gltf); + expect(gltf.buffers.length).toBe(1); + expect(gltf.buffers[0].extras._pipeline.source).toEqual(expectedBuffer); + }), done).toResolve(); }); }); diff --git a/specs/lib/numberOfComponentsForTypeSpec.js b/specs/lib/numberOfComponentsForTypeSpec.js index 623af603..23d8a1ef 100644 --- a/specs/lib/numberOfComponentsForTypeSpec.js +++ b/specs/lib/numberOfComponentsForTypeSpec.js @@ -3,5 +3,12 @@ var numberOfComponentsForType = require('../../lib/numberOfComponentsForType'); describe('numberOfComponentsForType', function() { it('numberOfComponentsForType', function() { + expect(numberOfComponentsForType('SCALAR')).toBe(1); + expect(numberOfComponentsForType('VEC2')).toBe(2); + expect(numberOfComponentsForType('VEC3')).toBe(3); + expect(numberOfComponentsForType('VEC4')).toBe(4); + expect(numberOfComponentsForType('MAT2')).toBe(4); + expect(numberOfComponentsForType('MAT3')).toBe(9); + expect(numberOfComponentsForType('MAT4')).toBe(16); }); }); diff --git a/specs/lib/parseGlbSpec.js b/specs/lib/parseGlbSpec.js index a7443d4e..9ae51661 100644 --- a/specs/lib/parseGlbSpec.js +++ b/specs/lib/parseGlbSpec.js @@ -1,7 +1,147 @@ 'use strict'; var parseGlb = require('../../lib/parseGlb'); +var removePipelineExtras = require('../../lib/removePipelineExtras'); describe('parseGlb', function() { - it('parseGlb', function() { + it('throws an error with invalid magic', function() { + var glb = Buffer.alloc(20); + glb.write('NOPE', 0); + expect(function() { + parseGlb(glb); + }).toThrowRuntimeError(); + }); + + it('throws an error if version is not 1 or 2', function() { + var glb = Buffer.alloc(20); + glb.write('glTF', 0); + glb.writeUInt32LE(3, 4); + expect(function() { + parseGlb(glb); + }).toThrowRuntimeError(); + }); + + describe('1.0', function() { + it('throws an error if content format is not JSON', function() { + var glb = Buffer.alloc(20); + glb.write('glTF', 0); + glb.writeUInt32LE(1, 4); + glb.writeUInt32LE(20, 8); + glb.writeUInt32LE(0, 12); + glb.writeUInt32LE(1, 16); + expect(function() { + parseGlb(glb); + }).toThrowRuntimeError(); + }); + + it('loads binary glTF', function() { + var binaryData = Buffer.from([0, 1, 2, 3, 4, 5]); + var gltf = { + bufferViews: { + imageBufferView: { + byteLength: 0 + }, + shaderBufferView: { + byteLength: 0 + } + }, + buffers: { + binary_glTF: { + byteLength: binaryData.length + } + }, + images: { + image: { + extensions: { + KHR_binary_glTF: { + bufferView: 'imageBufferView', + mimeType: 'image/jpg' + } + } + } + }, + shaders: { + shader: { + extensions: { + KHR_binary_glTF: { + bufferView: 'shaderBufferView' + } + } + } + }, + extensionsUsed: ['KHR_binary_glTF'] + }; + var gltfString = JSON.stringify(gltf); + while (gltfString.length % 4 !== 0) { + gltfString += ' '; + } + var glb = Buffer.alloc(20 + gltfString.length + binaryData.length); + glb.write('glTF', 0); + glb.writeUInt32LE(1, 4); + glb.writeUInt32LE(20 + gltfString.length + binaryData.length, 8); + glb.writeUInt32LE(gltfString.length, 12); + glb.writeUInt32LE(0, 16); + glb.write(gltfString, 20); + binaryData.copy(glb, 20 + gltfString.length); + + var parsedGltf = parseGlb(glb); + expect(parsedGltf.extensionsUsed).toBeUndefined(); + var buffer = parsedGltf.buffers.binary_glTF; + for (var i = 0; i < binaryData.length; i++) { + expect(buffer.extras._pipeline.source[i]).toEqual(binaryData[i]); + } + + var image = parsedGltf.images.image; + expect(image.extensions.KHR_binary_glTF).toBeDefined(); + expect(image.extensions.KHR_binary_glTF.bufferView).toBe('imageBufferView'); + expect(image.extensions.KHR_binary_glTF.mimeType).toBe('image/jpg'); + var shader = parsedGltf.shaders.shader; + expect(shader.extensions.KHR_binary_glTF).toBeDefined(); + expect(shader.extensions.KHR_binary_glTF.bufferView).toBe('shaderBufferView'); + }); + }); + + describe('2.0', function() { + it('loads binary glTF', function() { + var i; + var binaryData = Buffer.from([0, 1, 2, 3, 4, 5]); + var gltf = { + asset: { + version: '2.0' + }, + buffers: [ + { + byteLength: binaryData.length + } + ], + images: [ + { + bufferView: 0, + mimeType: 'image/jpg' + } + ] + }; + var gltfString = JSON.stringify(gltf); + while (gltfString.length % 4 !== 0) { + gltfString += ' '; + } + var glb = Buffer.alloc(28 + gltfString.length + binaryData.length); + glb.write('glTF', 0); + glb.writeUInt32LE(2, 4); + glb.writeUInt32LE(12 + 8 + gltfString.length + 8 + binaryData.length, 8); + glb.writeUInt32LE(gltfString.length, 12); + glb.writeUInt32LE(0x4E4F534A, 16); + glb.write(gltfString, 20); + glb.writeUInt32LE(binaryData.length, 20 + gltfString.length); + glb.writeUInt32LE(0x004E4942, 24 + gltfString.length); + binaryData.copy(glb, 28 + gltfString.length); + + var parsedGltf = parseGlb(glb); + var buffer = parsedGltf.buffers[0]; + for (i = 0; i < binaryData.length; i++) { + expect(buffer.extras._pipeline.source[i]).toEqual(binaryData[i]); + } + removePipelineExtras(parsedGltf); + expect(parsedGltf).toEqual(gltf); + }); }); }); diff --git a/specs/lib/processGlbSpec.js b/specs/lib/processGlbSpec.js index 7fe486c1..26dbf240 100644 --- a/specs/lib/processGlbSpec.js +++ b/specs/lib/processGlbSpec.js @@ -1,7 +1,22 @@ 'use strict'; +var fsExtra = require('fs-extra'); var processGlb = require('../../lib/processGlb'); +var glbPath = 'specs/data/2.0/box-textured-binary/box-textured-binary.glb'; + describe('processGlb', function() { - it('processGlb', function() { + it('processGlb', function(done) { + spyOn(console, 'log'); + var glb = fsExtra.readFileSync(glbPath); + var options = { + separate: true, + stats: true + }; + expect(processGlb(glb, options) + .then(function(results) { + expect(Buffer.isBuffer(results.glb)).toBe(true); + expect(results.separateResources).toBeDefined(); + expect(console.log).toHaveBeenCalled(); + }), done).toResolve(); }); }); diff --git a/specs/lib/processGltfSpec.js b/specs/lib/processGltfSpec.js index c3432126..2b0fa522 100644 --- a/specs/lib/processGltfSpec.js +++ b/specs/lib/processGltfSpec.js @@ -1,7 +1,24 @@ 'use strict'; +var fsExtra = require('fs-extra'); var processGltf = require('../../lib/processGltf'); +var gltfPath = 'specs/data/2.0/box-textured-embedded/box-textured-embedded.gltf'; + + describe('processGltf', function() { - it('processGltf', function() { + it('processGltf', function(done) { + spyOn(console, 'log'); + var gltf = fsExtra.readJsonSync(gltfPath); + var options = { + separate: true, + stats: true + }; + expect(processGltf(gltf, options) + .then(function(results) { + expect(results.gltf).toBeDefined(); + expect(results.separateResources).toBeDefined(); + expect(results.gltf.buffers.length).toBe(1); + expect(console.log).toHaveBeenCalled(); + }), done).toResolve(); }); }); diff --git a/specs/lib/readAccessorPackedSpec.js b/specs/lib/readAccessorPackedSpec.js new file mode 100644 index 00000000..34abed01 --- /dev/null +++ b/specs/lib/readAccessorPackedSpec.js @@ -0,0 +1,87 @@ +'use strict'; +var Cesium = require('cesium'); +var readAccessorPacked = require('../../lib/readAccessorPacked'); +var readResources = require('../../lib/readResources'); + +var arrayFill = Cesium.arrayFill; + +var contiguousData = [ + -1.0, 1.0, -1.0, + 0.0, 0.0, 0.0, + 3.0, 2.0, 1.0, + -1.0, -2.0, -3.0 +]; + +var nan = Number.NaN; +var nonContiguousData = [ + -1.0, 1.0, -1.0, + nan, nan, nan, + 0.0, 0.0, 0.0, + nan, nan, nan, + 3.0, 2.0, 1.0, + nan, nan, nan, + -1.0, -2.0, -3.0, + nan, nan, nan +]; + +function createGltf(elements, byteStride) { + var buffer = Buffer.from((new Float32Array(elements)).buffer); + var byteLength = buffer.length; + var dataUri = 'data:application/octet-stream;base64,' + buffer.toString('base64'); + var gltf = { + accessors: [ + { + bufferView: 0, + byteOffset: 0, + componentType: 5126, + count: 4, + type: 'VEC3' + } + ], + bufferViews: [ + { + buffer: 0, + byteOffset: 0, + byteLength: byteLength, + byteStride: byteStride + } + ], + buffers: [ + { + uri: dataUri, + byteLength: byteLength + } + ] + }; + return readResources(gltf); +} + +describe('readAccessorPacked', function() { + it('reads contiguous accessor', function(done) { + expect(createGltf(contiguousData, 12) + .then(function(gltf) { + expect(readAccessorPacked(gltf, gltf.accessors[0])).toEqual(contiguousData); + }), done).toResolve(); + }); + + it('reads non-contiguous accessor', function(done) { + expect(createGltf(nonContiguousData, 24) + .then(function(gltf) { + expect(readAccessorPacked(gltf, gltf.accessors[0])).toEqual(contiguousData); + }), done).toResolve(); + }); + + it('reads accessor that does not have a buffer view', function() { + var gltf = { + accessors: [ + { + componentType: 5126, + count: 4, + type: 'VEC3' + } + ] + }; + var expected = arrayFill(new Array(12), 0); // All zeroes + expect(readAccessorPacked(gltf, gltf.accessors[0])).toEqual(expected); + }); +}); diff --git a/specs/lib/readResourcesSpec.js b/specs/lib/readResourcesSpec.js index 1b2d0fb3..b0e58a03 100644 --- a/specs/lib/readResourcesSpec.js +++ b/specs/lib/readResourcesSpec.js @@ -40,7 +40,7 @@ function readsResources(gltfPath, binary, separate, done) { var gltf = readGltf(gltfPath, binary); var resourceDirectory = path.dirname(gltfPath); var options = { - resourceDirectory : resourceDirectory + resourceDirectory: resourceDirectory }; expect(readResources(gltf, options) .then(function(gltf) { @@ -75,8 +75,8 @@ function readsResources(gltfPath, binary, separate, done) { function checkTransparency(gltfPath, check, result) { var gltf = readGltf(gltfPath, false); var options = { - resourceDirectory : path.dirname(gltfPath), - checkTransparency : check + resourceDirectory: path.dirname(gltfPath), + checkTransparency: check }; return readResources(gltf, options) .then(function(gltf) { diff --git a/specs/lib/removeDefaultsSpec.js b/specs/lib/removeDefaultsSpec.js index a67ce4b1..1441ed2b 100644 --- a/specs/lib/removeDefaultsSpec.js +++ b/specs/lib/removeDefaultsSpec.js @@ -3,5 +3,25 @@ var removeDefaults = require('../../lib/removeDefaults'); describe('removeDefaults', function() { it('removeDefaults', function() { + var gltf = { + nodes: [ + { + matrix: [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1] + }, + { + matrix: [2,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1] + } + ], + accessors: [ + { + bufferView: 0, + normalized: false + } + ] + }; + removeDefaults(gltf); + expect(gltf.nodes[0].matrix).toBeUndefined(); + expect(gltf.nodes[1].matrix).toBeDefined(); + expect(gltf.accessors[0].normalized).toBeUndefined(); }); }); diff --git a/specs/lib/removeExtensionsRequiredSpec.js b/specs/lib/removeExtensionsRequiredSpec.js index 887a1ac5..adeb1f41 100644 --- a/specs/lib/removeExtensionsRequiredSpec.js +++ b/specs/lib/removeExtensionsRequiredSpec.js @@ -2,6 +2,25 @@ var removeExtensionsRequired = require('../../lib/removeExtensionsRequired'); describe('removeExtensionsRequired', function() { - it('removeExtensionsRequired', function() { + it('removes extension from extensionsRequired', function() { + var gltf = { + extensionsRequired: [ + 'extension1', + 'extension2' + ], + extensionsUsed: [ + 'extension1', + 'extension2' + ] + }; + removeExtensionsRequired(gltf, 'extension1'); + expect(gltf.extensionsRequired).toEqual(['extension2']); + removeExtensionsRequired(gltf, 'extension2'); + expect(gltf.extensionsRequired).toBeUndefined(); + expect(gltf.extensionsUsed).toEqual(['extension1', 'extension2']); + + var emptyGltf = {}; + removeExtensionsRequired(gltf, 'extension1'); + expect(emptyGltf).toEqual({}); }); }); diff --git a/specs/lib/removeExtensionsUsedSpec.js b/specs/lib/removeExtensionsUsedSpec.js index 1f9d0cda..d8c1867e 100644 --- a/specs/lib/removeExtensionsUsedSpec.js +++ b/specs/lib/removeExtensionsUsedSpec.js @@ -2,6 +2,27 @@ var removeExtensionsUsed = require('../../lib/removeExtensionsUsed'); describe('removeExtensionsUsed', function() { - it('removeExtensionsUsed', function() { + it('removes extension from extensionsUsed', function() { + var gltf = { + extensionsRequired: [ + 'extension1', + 'extension2' + ], + extensionsUsed: [ + 'extension1', + 'extension2' + ] + }; + removeExtensionsUsed(gltf, 'extension1'); + expect(gltf.extensionsRequired).toEqual(['extension2']); + expect(gltf.extensionsUsed).toEqual(['extension2']); + + removeExtensionsUsed(gltf, 'extension2'); + expect(gltf.extensionsRequired).toBeUndefined(); + expect(gltf.extensionsUsed).toBeUndefined(); + + var emptyGltf = {}; + removeExtensionsUsed(gltf, 'extension1'); + expect(emptyGltf).toEqual({}); }); }); diff --git a/specs/lib/removePipelineExtrasSpec.js b/specs/lib/removePipelineExtrasSpec.js index 09b5be36..3683afb3 100644 --- a/specs/lib/removePipelineExtrasSpec.js +++ b/specs/lib/removePipelineExtrasSpec.js @@ -1,7 +1,46 @@ 'use strict'; +var Cesium = require('cesium'); +var addPipelineExtras = require('../../lib/addPipelineExtras'); var removePipelineExtras = require('../../lib/removePipelineExtras'); +var WebGLConstants = Cesium.WebGLConstants; + describe('removePipelineExtras', function() { - it('removePipelineExtras', function() { + it('removes pipeline extras', function() { + // TODO KHR_techniques_webgl: remove shaders from top level, put inside extras.KHR_techniques_webgl + var gltf = { + buffers: [ + { + byteLength: 100 + } + ], + shaders: [ + { + type: WebGLConstants.VERTEX_SHADER, + uri: 'data:,' + } + ], + images: [ + { + extras: { + compressedImage3DTiles: { + s3tc: { + uri: 'data:,' + }, + etc1: { + uri: 'data:,' + } + } + } + } + ] + }; + var gltfWithExtrasRemoved = removePipelineExtras(addPipelineExtras(gltf)); + expect(gltfWithExtrasRemoved.buffers[0].extras).toBeUndefined(); + expect(gltfWithExtrasRemoved.shaders[0].extras).toBeUndefined(); + expect(gltfWithExtrasRemoved.images[0].extras._pipeline).toBeUndefined(); + expect(gltfWithExtrasRemoved.images[0].extras.compressedImage3DTiles.s3tc.extras).toBeUndefined(); + expect(gltfWithExtrasRemoved.images[0].extras.compressedImage3DTiles.etc1.extras).toBeUndefined(); + expect(gltfWithExtrasRemoved.extras).toBeUndefined(); }); }); diff --git a/specs/lib/removeUnusedElementsSpec.js b/specs/lib/removeUnusedElementsSpec.js new file mode 100644 index 00000000..77490704 --- /dev/null +++ b/specs/lib/removeUnusedElementsSpec.js @@ -0,0 +1,306 @@ +'use strict'; +var Cesium = require('cesium'); +var ForEach = require('../../lib/ForEach'); +var removeUnusedElements = require('../../lib/removeUnusedElements'); + +var WebGLConstants = Cesium.WebGLConstants; + +var gltf = { + nodes: [ + { + skin: 0, + mesh: 0, + translation: [0.0, 0.0, 0.0] + } + ], + buffers: [ + { + name: 'mesh', + byteLength: 246, + uri: 'data0.bin' + }, + { + name: 'other', + byteLength: 80, + uri: 'data1.bin' + }, + { + name: 'image01', + byteLength: 1000, + uri: 'data2.bin' + }, + { + image: 'image2', + byteLength: 500, + uri: 'data3.bin' + } + ], + bufferViews: [ + { + name: 'positions', + buffer: 0, + byteOffset: 0, + byteLength: 36 + }, + { + name: 'normals', + buffer: 0, + byteOffset: 36, + byteLength: 36 + }, + { + name: 'texcoords', + buffer: 0, + byteOffset: 72, + byteLength: 24 + }, + { + name: 'positions-target0', + buffer: 0, + byteOffset: 96, + byteLength: 36 + }, + { + name: 'normals-target0', + buffer: 0, + byteOffset: 132, + byteLength: 36 + }, + { + name: 'positions-target1', + buffer: 0, + byteOffset: 168, + byteLength: 36 + }, + { + name: 'normals-target1', + buffer: 0, + byteOffset: 204, + byteLength: 36 + }, + { + name: 'indices', + buffer: 0, + byteOffset: 240, + byteLength: 6 + }, + { + name: 'other', + buffer: 1, + byteOffset: 0, + byteLength: 80 + }, + { + name: 'image0', + buffer: 2, + byteOffset: 0, + byteLength: 500 + }, + { + name: 'image1', + buffer: 2, + byteOffset: 500, + byteLength: 500 + }, + { + name: 'image2', + buffer: 3, + byteOffset: 1000, + byteLength: 500 + } + ], + accessors: [ + { + name: 'positions', + bufferView: 0, + byteOffset: 0, + componentType: WebGLConstants.FLOAT, + count: 3, + type: 'VEC3', + min: [-1.0, -1.0, -1.0], + max: [1.0, 1.0, 1.0] + }, + { + name: 'normals', + bufferView: 1, + byteOffset: 36, + componentType: WebGLConstants.FLOAT, + count: 3, + type: 'VEC3' + }, + { + name: 'texcoords', + bufferView: 2, + byteOffset: 72, + componentType: WebGLConstants.FLOAT, + count: 3, + type: 'VEC2' + }, + { + name: 'positions-target0', + bufferView: 3, + byteOffset: 96, + componentType: WebGLConstants.FLOAT, + count: 3, + type: 'VEC3', + min: [-1.0, -1.0, -1.0], + max: [1.0, 1.0, 1.0] + }, + { + name: 'normals-target0', + bufferView: 4, + byteOffset: 132, + componentType: WebGLConstants.FLOAT, + count: 3, + type: 'VEC3' + }, + { + name: 'positions-target1', + bufferView: 5, + byteOffset: 168, + componentType: WebGLConstants.FLOAT, + count: 3, + type: 'VEC3', + min: [-1.0, -1.0, -1.0], + max: [1.0, 1.0, 1.0] + }, + { + name: 'normals-target1', + bufferView: 6, + byteOffset: 204, + componentType: WebGLConstants.FLOAT, + count: 3, + type: 'VEC3' + }, + { + name: 'indices', + bufferView: 7, + byteOffset: 240, + componentType: WebGLConstants.UNSIGNED_SHORT, + count: 3, + type: 'SCALAR' + }, + { + name: 'bone-matrix', + bufferView: 8, + byteOffset: 0, + componentType: WebGLConstants.FLOAT, + count: 1, + type: 'MAT4' + }, + { + name: 'times', + bufferView: 8, + byteOffset: 64, + componentType: WebGLConstants.FLOAT, + count: 1, + type: 'SCALAR' + }, + { + name: 'translations', + bufferView: 8, + byteOffset: 68, + componentType: WebGLConstants.FLOAT, + count: 1, + type: 'VEC3' + } + ], + meshes: [ + { + primitives: [ + { + attributes: { + POSITION: 0, + NORMAL: 1, + TEXCOORD_0: 2 + }, + targets: [ + { + POSITION: 3, + NORMAL: 4 + }, + { + POSITION: 5, + NORMAL: 6 + } + ], + indices: 7, + mode: WebGLConstants.TRIANGLES + } + ] + } + ], + skins: [ + { + inverseBindMatrices: 8, + joints: [0] + } + ], + animations: [ + { + channels: [ + { + sampler: 0, + target: { + node: 0, + path: 'translation' + } + } + ], + samplers: [ + { + input: 9, + output: 10 + } + ] + } + ], + images: [ + { + bufferView: 9, + mimeType: 'image/png' + }, + { + bufferView: 10, + mimeType: 'image/png' + }, + { + bufferView: 11, + mimeType: 'image/png' + } + ] +}; + +describe('removeUnusedElements', function() { + it('removes unused accessors, bufferViews, and buffers', function() { + delete gltf.animations; + delete gltf.skins; + gltf.meshes[0].primitives[0].targets.splice(0, 1); + gltf.images.splice(1, 2); + removeUnusedElements(gltf); + + var remainingAccessorNames = ['positions', 'normals', 'texcoords', 'positions-target1', 'normals-target1', 'indices']; + var remainingAcessorBufferViewIds = [0, 1, 2, 3, 4, 5]; + var remainingBufferViewNames = ['positions', 'normals', 'texcoords', 'positions-target1', 'normals-target1', 'indices', 'image0']; + var remainingBufferViewBufferIds = [0, 0, 0, 0, 0, 0, 1]; + var remainingBufferNames = ['mesh', 'image01']; + + expect(gltf.accessors.length).toBe(remainingAccessorNames.length); + expect(gltf.bufferViews.length).toBe(remainingBufferViewNames.length); + expect(gltf.buffers.length).toBe(remainingBufferNames.length); + + ForEach.accessor(gltf, function(accessor, index) { + expect(accessor.name).toBe(remainingAccessorNames[index]); + expect(accessor.bufferView).toBe(remainingAcessorBufferViewIds[index]); + }); + + ForEach.bufferView(gltf, function(bufferView, index) { + expect(bufferView.name).toBe(remainingBufferViewNames[index]); + expect(bufferView.buffer).toBe(remainingBufferViewBufferIds[index]); + }); + + ForEach.buffer(gltf, function(buffer, index) { + expect(buffer.name).toBe(remainingBufferNames[index]); + }); + }); +}); diff --git a/specs/lib/updateVersionSpec.js b/specs/lib/updateVersionSpec.js index d913289c..3482097d 100644 --- a/specs/lib/updateVersionSpec.js +++ b/specs/lib/updateVersionSpec.js @@ -1,8 +1,614 @@ 'use strict'; +var Cesium = require('cesium'); +var ForEach = require('../../lib/ForEach'); +var numberOfComponentsForType = require('../../lib/numberOfComponentsForType'); +var readResources = require('../../lib/readResources'); var updateVersion = require('../../lib/updateVersion'); +var Cartesian3 = Cesium.Cartesian3; +var Quaternion = Cesium.Quaternion; +var WebGLConstants = Cesium.WebGLConstants; + describe('updateVersion', function() { - // Move byteOffset to bufferView works well for the first test model - it('updateVersion', function() { + it('defaults to 1.0 if gltf has no version', function() { + var gltf = {}; + updateVersion(gltf, { + targetVersion: '1.0' + }); + expect(gltf.asset.version).toEqual('1.0'); + }); + + it('updates empty glTF with version from 0.8 to 2.0', function() { + var gltf = { + version: '0.8' + }; + updateVersion(gltf); + expect(gltf.version).toBeUndefined(); + expect(gltf.asset.version).toEqual('2.0'); + }); + + it('updates empty glTF with version 1.0 to 2.0', function() { + var gltf = { + asset: { + version: '1.0' + } + }; + updateVersion(gltf); + expect(gltf.asset.version).toEqual('2.0'); + }); + + it('updates a glTF with non-standard version to 2.0', function() { + var gltf = { + asset: { + version: '1.0.1' + } + }; + updateVersion(gltf); + expect(gltf.asset.version).toEqual('2.0'); + }); + + it('updates glTF from 0.8 to 1.0', function(done) { + var times = [0.0, 1.0]; + var axisA = new Cartesian3(0.0, 0.0, 1.0); + var axisB = new Cartesian3(0.0, 1.0, 0.0); + var angleA = 0.0; + var angleB = 0.5; + var quatA = Quaternion.fromAxisAngle(axisA, angleA); + var quatB = Quaternion.fromAxisAngle(axisB, angleB); + + var originalBuffer = Buffer.from((new Float32Array([times[0], times[1], axisA.x, axisA.y, axisA.z, angleA, axisB.x, axisB.y, axisB.z, angleB])).buffer); + var expectedBuffer = Buffer.from((new Float32Array([times[0], times[1], quatA.x, quatA.y, quatA.z, quatA.w, quatB.x, quatB.y, quatB.z, quatB.w])).buffer); + + var dataUri = 'data:application/octet-stream;base64,' + originalBuffer.toString('base64'); + + var gltf = { + version: '0.8', + asset: { + profile: 'WebGL 1.0' + }, + allExtensions: [ + 'extension' + ], + lights: { + 'someLight': true + }, + buffers: { + buffer: { + uri: dataUri + } + }, + bufferViews: { + bufferViewTime: { + buffer: 'buffer', + byteLength: 8, + byteOffset: 0 + }, + bufferViewRotation: { + buffer: 'buffer', + byteLength: 32, + byteOffset: 8 + } + }, + accessors: { + accessorTime: { + bufferView: "bufferViewTime", + byteOffset: 0, + byteStride: 0, + componentType: WebGLConstants.FLOAT, + count: 2, + type: 'SCALAR' + }, + accessorRotation: { + bufferView: "bufferViewRotation", + byteOffset: 0, + byteStride: 0, + componentType: WebGLConstants.FLOAT, + count: 2, + type: 'VEC4' + } + }, + animations: { + animationNode: { + channels: [ + { + sampler: 'sampler', + target: { + id: 'node', + path: 'rotation' + } + } + ], + count: 2, + parameters: { + time: 'accessorTime', + rotation: 'accessorRotation' + }, + samplers: { + sampler: { + input: 'time', + interpolation: 'LINEAR', + output: 'rotation' + } + } + }, + animationNodeOther: { + channels: [ + { + sampler: 'sampler', + target: { + id: 'nodeOther', + path: 'rotation' + } + } + ], + count: 2, + parameters: { + time: 'accessorTime', + rotation: 'accessorRotation' + }, + samplers: { + sampler: { + input: 'time', + interpolation: 'LINEAR', + output: 'rotation' + } + } + } + }, + materials: { + material: { + instanceTechnique: { + technique: 'technique', + values: { + ambient: [0.0, 0.0, 0.0, 1.0] + } + } + } + }, + meshes: { + mesh: { + primitives: [ + { + primitive: WebGLConstants.TRIANGLES + } + ] + } + }, + nodes: { + node: { + rotation: [0.0, 0.0, 1.0, 0.0], + instanceSkin: { + skeletons: ['skeleton'], + skin: 'skin', + meshes: ['mesh'] + } + }, + nodeOther: { + rotation: [0.0, 0.0, 1.0, 0.0] + } + }, + techniques: { + technique: { + pass: 'pass', + passes: { + pass: { + instanceProgram: { + attributes: { + attribute: 'TEST_ATTRIBUTE' + }, + program: 'program', + uniforms: { + uniform: 'TEST_UNIFORM' + } + }, + states: ['TEST_STATE'] + } + } + } + } + }; + + expect(readResources(gltf) + .then(function(gltf) { + updateVersion(gltf, { + targetVersion: '1.0' + }); + + // Asset updates: version set to 1.0, profile split into object + expect(gltf.asset.version).toEqual('1.0'); + expect(gltf.asset.profile).toEqual({ + api: 'WebGL', + version: '1.0' + }); + + // Top-level version removed + expect(gltf.version).toBeUndefined(); + + // allExtensions renamed to extensionsUsed + // gltf.lights moved to KHR_materials_common extension + expect(gltf.extensionsUsed).toEqual(['extension', 'KHR_materials_common']); + expect(gltf.allExtensions).toBeUndefined(); + expect(gltf.extensions.KHR_materials_common.lights).toEqual({ + someLight: true + }); + + // material.instanceTechnique properties moved onto the material directly + var material = gltf.materials.material; + expect(material.technique).toEqual('technique'); + expect(material.values).toEqual({ + ambient: [0.0, 0.0, 0.0, 1.0] + }); + + // primitive.primitive renamed to primitive.mode + var primitive = gltf.meshes.mesh.primitives[0]; + expect(primitive.primitive).toBeUndefined(); + expect(primitive.mode).toEqual(WebGLConstants.TRIANGLES); + + // node.instanceSkin is split into node.skeletons, node.skin, and node.meshes + var node = gltf.nodes.node; + expect(node.skeletons).toEqual(['skeleton']); + expect(node.skin).toEqual('skin'); + expect(node.meshes).toEqual(['mesh']); + + // Node rotation converted from axis-angle to quaternion + expect(node.rotation).toEqual([0.0, 0.0, 0.0, 1.0]); + + // Technique pass and passes removed + var technique = gltf.techniques.technique; + expect(technique.pass).toBeUndefined(); + expect(technique.passes).toBeUndefined(); + expect(technique.attributes).toEqual({ + attribute: 'TEST_ATTRIBUTE' + }); + expect(technique.program).toEqual('program'); + expect(technique.uniforms).toEqual({ + uniform: 'TEST_UNIFORM' + }); + expect(technique.states).toEqual(['TEST_STATE']); + + // Animation rotations converted from axis-angle to quaternion + var buffer = gltf.buffers.buffer.extras._pipeline.source; + expect(buffer.equals(expectedBuffer)).toBe(true); + }), done).toResolve(); + }); + + function getNodeByName(gltf, name) { + var nodeWithName; + ForEach.node(gltf, function(node) { + if (node.name === name) { + nodeWithName = node; + } + }); + return nodeWithName; + } + + it('updates glTF from 1.0 to 2.0', function(done) { + var source = Buffer.from((new Int16Array([-2.0, 1.0, 0.0, 1.0, 2.0, 3.0])).buffer); + var dataUri = 'data:application/octet-stream;base64,' + source.toString('base64'); + + var gltf = { + asset: { + profile: { + api: 'WebGL', + version: '1.0.3' + }, + version: '1.0' + }, + animations: { + animation: { + samplers: { + sampler: { + input: 'INPUT', + output: 'OUTPUT' + } + }, + parameters: { + INPUT: 'accessor_input', + OUTPUT: 'accessor_output' + } + } + }, + extensionsUsed: [ + 'KHR_materials_common', + 'WEB3D_quantized_attributes', + 'UNKOWN_EXTENSION' + ], + meshes: { + mesh: { + primitives: [ + { + attributes: { + POSITION: 'accessor_position', + NORMAL: 'accessor_normal', + TEXCOORD: 'accessor_texcoord', + COLOR: 'accessor_color', + APPLICATIONSPECIFIC: 'accessor', + _TEMPERATURE: 'accessor_temperature' + }, + indices: 'accessor_indices' + } + ] + } + }, + materials: { + material: { + values: { + shininess: 1.0 + } + } + }, + techniques: { + technique: { + states: { + enable: [ WebGLConstants.SCISSOR_TEST ], + functions: { + blendColor: [-1.0, 0.0, 0.0, 2.0], + depthRange: [1.0, -1.0], + scissor: [0.0, 0.0, 0.0, 0.0] + } + }, + attributes: { + a_application: 'application' + }, + parameters: { + lightAttenuation: { + value: 1.0 + }, + texcoord: { + semantic: 'TEXCOORD' + }, + color: { + semantic: 'COLOR' + }, + application: { + semantic: 'APPLICATIONSPECIFIC', + count: 1, + value: 2 + }, + jointMatrix: { + semantic: 'JOINTMATRIX', + count: 2 + }, + notJointMatrix: { + count: 3 + }, + notJointMatrixWithSemantic: { + count: 4 + } + } + } + }, + accessors: { + accessor: { + byteOffset: 0, + bufferView: 'bufferView', + componentType: WebGLConstants.SHORT, + count: 6, + type: 'SCALAR' + }, + accessor_indices: { + componentType: WebGLConstants.UNSIGNED_INT, + count: 3, + type: 'SCALAR' + }, + accessor_input: { + componentType: WebGLConstants.FLOAT, + count: 1, + type: 'SCALAR' + }, + accessor_output: { + componentType: WebGLConstants.FLOAT, + count: 1, + type: 'VEC3' + }, + accessor_position: { + bufferView: 'bufferViewAttributes', + byteOffset: 0, + componentType: WebGLConstants.FLOAT, + count: 3, + type: 'VEC3' + }, + accessor_normal: { + bufferView: 'bufferViewAttributes', + byteOffset: 36, + componentType: WebGLConstants.FLOAT, + count: 3, + type: 'VEC3' + }, + accessor_texcoord: { + bufferView: 'bufferViewAttributes', + byteOffset: 72, + componentType: WebGLConstants.FLOAT, + count: 3, + type: 'VEC2' + }, + accessor_color: { + componentType: WebGLConstants.FLOAT, + count: 3, + type: 'VEC3' + }, + accessor_temperature: { + componentType: WebGLConstants.FLOAT, + count: 2, + type: 'SCALAR' + } + }, + bufferViews: { + bufferView: { + buffer: 'buffer', + byteOffset: 96 + }, + bufferViewAttributes: { + buffer: 'buffer', + byteOffset: 12 + } + }, + buffers: { + buffer: { + type: 'arraybuffer', + uri: dataUri + } + }, + cameras: { + camera: { + perspective: { + aspectRatio: 0.0, + yfov: 0.0 + } + } + }, + nodes: { + rootTransform: { + children: [ + 'skeletonNode', + 'meshNode' + ], + matrix: [ + 1, 0, 0, 0, + 0, 0, -1, 0, + 0, 1, 0, 0, + 0, 0, 0, 1 + ] + }, + skeletonNode: {}, + meshNode: { + skin: 'someSkin', + skeletons: [ + 'skeletonNode' + ] + }, + nodeWithoutChildren: { + children: [] + } + }, + samplers: [], + scene: 'defaultScene', + scenes: { + defaultScene: { + nodes: [ + 'rootTransform' + ] + } + }, + skins: { + someSkin: {} + }, + textures: [ + { + source: 0, + format: WebGLConstants.RGBA, + internalFormat: WebGLConstants.RGBA, + target: WebGLConstants.TEXTURE_2D, + type: WebGLConstants.UNSIGNED_BYTE + } + ] + }; + + expect(readResources(gltf) + .then(function(gltf) { + updateVersion(gltf); + + // Asset updates: version set to 2.0, profile removed + expect(gltf.asset.version).toEqual('2.0'); + expect(gltf.asset.profile).toBeUndefined(); + + // Extensions used become extensions required + var extensionsUsed = gltf.extensionsUsed; + expect(extensionsUsed).toEqual([ + 'KHR_materials_common', + 'WEB3D_quantized_attributes', + 'UNKOWN_EXTENSION', + 'KHR_technique_webgl' + ]); + var extensionsRequired = gltf.extensionsRequired; + expect(extensionsRequired).toEqual([ + 'KHR_materials_common', + 'WEB3D_quantized_attributes', + 'KHR_technique_webgl' + ]); + + // animation.parameters removed + var animation = gltf.animations[0]; + var sampler = animation.samplers[0]; + expect(sampler.input).toEqual(2); + expect(sampler.output).toEqual(3); + expect(animation.parameters).toBeUndefined(); + + // Empty arrays removed + expect(gltf.samplers).toBeUndefined(); + expect(getNodeByName(gltf, 'nodeWithoutChildren').children).toBeUndefined(); + + // Remove value from attribute but not uniform in material technique + var material = gltf.materials[0]; + expect(material.values.shininess).toEqual(1); + var technique = gltf.techniques[0]; + expect(technique.parameters.lightAttenuation.value).toEqual(1.0); + expect(technique.parameters.application.value).toBeUndefined(); + + // TEXCOORD and COLOR are now TEXCOORD_0 and COLOR_0 + var primitive = gltf.meshes[0].primitives[0]; + expect(technique.parameters.texcoord.semantic).toEqual('TEXCOORD_0'); + expect(technique.parameters.color.semantic).toEqual('COLOR_0'); + expect(primitive.attributes.TEXCOORD).toBeUndefined(); + expect(primitive.attributes.TEXCOORD_0).toEqual(6); + expect(primitive.attributes.COLOR).toBeUndefined(); + expect(primitive.attributes.COLOR_0).toEqual(7); + + // Underscore added to application specific attributes + expect(technique.parameters.application.semantic).toEqual('_APPLICATIONSPECIFIC'); + expect(primitive.attributes.APPLICATIONSPECIFIC).toBeUndefined(); + expect(primitive.attributes._APPLICATIONSPECIFIC).toEqual(0); + expect(primitive.attributes._TEMPERATURE).toEqual(8); + + // TODO : KHR_techniques_webl these checks will be removed + var states = technique.states; + expect(states.enable).toEqual([]); + expect(states.functions.scissor).toBeUndefined(); + expect(states.functions.blendColor).toEqual([0.0, 0.0, 0.0, 1.0]); + expect(states.functions.depthRange).toEqual([0.0, 0.0]); + + // Clamp camera parameters + var camera = gltf.cameras[0]; + expect(camera.perspective.aspectRatio).toBeUndefined(); + expect(camera.perspective.yfov).toEqual(1.0); + + // Sets byteLength for buffers and bufferViews + var buffer = gltf.buffers[0]; + expect(buffer.type).toBeUndefined(); + expect(buffer.byteLength).toEqual(source.length); + var bufferView = gltf.bufferViews[0]; + expect(bufferView.byteLength).toEqual(source.length); + + // Only JOINTMATRIX or application specific semantics may have a count + expect(technique.parameters.application.count).toEqual(1); + expect(technique.parameters.jointMatrix.count).toEqual(2); + expect(technique.parameters.notJointMatrix.count).toBeUndefined(); + expect(technique.parameters.notJointMatrixWithSemantic.count).toBeUndefined(); + + // Min and max are added to all accessors + ForEach.accessor(gltf, function(accessor) { + expect(accessor.min.length).toEqual(numberOfComponentsForType(accessor.type)); + expect(accessor.max.length).toEqual(numberOfComponentsForType(accessor.type)); + }); + + // byteStride moved from accessor to bufferView + var positionAccessor = gltf.accessors[primitive.attributes.POSITION]; + var normalAccessor = gltf.accessors[primitive.attributes.NORMAL]; + var texcoordAccessor = gltf.accessors[primitive.attributes.TEXCOORD_0]; + var positionBufferView = gltf.bufferViews[positionAccessor.bufferView]; + var texcoordBufferView = gltf.bufferViews[texcoordAccessor.bufferView]; + expect(positionAccessor.bufferView).toBe(1); + expect(normalAccessor.bufferView).toBe(1); + expect(texcoordAccessor.bufferView).toBe(2); + expect(positionBufferView.byteLength).toBe(72); + expect(positionBufferView.byteOffset).toBe(12); // First unrelated buffer view is 12 bytes + expect(positionBufferView.byteStride).toBe(12); + expect(texcoordBufferView.byteLength).toBe(24); + expect(texcoordBufferView.byteOffset).toBe(72 + 12); // First unrelated buffer view is 12 bytes + expect(texcoordBufferView.byteStride).toBe(8); + expect(positionAccessor.byteStride).toBeUndefined(); + expect(normalAccessor.byteStride).toBeUndefined(); + expect(texcoordAccessor.byteStride).toBeUndefined(); + }), done).toResolve(); }); }); diff --git a/specs/lib/writeResourcesSpec.js b/specs/lib/writeResourcesSpec.js index b3ca8d5b..9e254ae3 100644 --- a/specs/lib/writeResourcesSpec.js +++ b/specs/lib/writeResourcesSpec.js @@ -10,7 +10,7 @@ var writeResources = require('../../lib/writeResources'); var gltfPath = 'specs/data/2.0/box-textured-embedded/box-textured-embedded.gltf'; var gltf; -// TODO : tests for shaders +// TODO : KHR_techniques_webgl - add test for shaders describe('writeResources', function() { beforeEach(function(done) { @@ -38,12 +38,12 @@ describe('writeResources', function() { }), done).toResolve(); }); - it('writes resources as files with default names', function(done) { + it('writes resources as files', function(done) { var separateResources = {}; var options = { - separateBuffers : true, - separateTextures : true, - separateResources : separateResources + separateBuffers: true, + separateTextures: true, + separateResources: separateResources }; var originalBufferViewsLength = gltf.bufferViews.length; var originalByteLength = gltf.buffers[0].byteLength; @@ -67,9 +67,9 @@ describe('writeResources', function() { it('writes resources as files with object names', function(done) { var separateResources = {}; var options = { - separateBuffers : true, - separateTextures : true, - separateResources : separateResources + separateBuffers: true, + separateTextures: true, + separateResources: separateResources }; gltf.buffers[0].name = 'my-buffer'; gltf.images[0].name = 'my-image'; @@ -83,10 +83,10 @@ describe('writeResources', function() { it('writes resources as files with gltf name', function(done) { var separateResources = {}; var options = { - name : 'my-gltf', - separateBuffers : true, - separateTextures : true, - separateResources : separateResources + name: 'my-gltf', + separateBuffers: true, + separateTextures: true, + separateResources: separateResources }; expect(writeResources(gltf, options) .then(function() { @@ -96,7 +96,7 @@ describe('writeResources', function() { }); it('writes resources as data uris', function(done) { var options = { - dataUris : true + dataUris: true }; var originalBufferViewsLength = gltf.bufferViews.length; var originalByteLength = gltf.buffers[0].byteLength;