From 0960548fa4611b43fec02d55b69c7fbb87f1bd91 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Fri, 13 May 2016 15:35:34 -0400 Subject: [PATCH 01/36] Added quantize attributes function and test skeleton --- lib/quantizeAttributes.js | 325 +++++++++++++++++++++++++ specs/data/quantized/Box.bin | Bin 0 -> 648 bytes specs/data/quantized/Box.gltf | 240 ++++++++++++++++++ specs/data/quantized/Box0FS.glsl | 17 ++ specs/data/quantized/Box0VS.glsl | 12 + specs/data/quantized/Duck.gltf | 362 ++++++++++++++++++++++++++++ specs/lib/quantizeAttributesSpec.js | 38 +++ 7 files changed, 994 insertions(+) create mode 100644 lib/quantizeAttributes.js create mode 100644 specs/data/quantized/Box.bin create mode 100644 specs/data/quantized/Box.gltf create mode 100644 specs/data/quantized/Box0FS.glsl create mode 100644 specs/data/quantized/Box0VS.glsl create mode 100644 specs/data/quantized/Duck.gltf create mode 100644 specs/lib/quantizeAttributesSpec.js diff --git a/lib/quantizeAttributes.js b/lib/quantizeAttributes.js new file mode 100644 index 00000000..e04aa87d --- /dev/null +++ b/lib/quantizeAttributes.js @@ -0,0 +1,325 @@ +'use strict'; +var Cesium = require('cesium'); +var defined = Cesium.defined; +module.exports = quantizeAttributes; + +/** + * + * @param gltf + * @param options + * + * @returns glTF with quantized attributes + * + * @example + * // Quantize the positions of a simple mesh + * var gltf = { + * "accessors": { + * "accessor_0": {...} + * "accessor_1": {...} + * }, + * "meshes": { + * "geometry": { + * "name": "Mesh", + * "primitives": [ + * { + * "attributes": { + * "NORMAL": "accessor_0", + * "POSITION": "accessor_1" + * } + * } + * ] + * } + * } + * }; + * + * var options = { + * "attributes": { + * "geometry": { + * "0": ["POSITION"] + * } + * } + * }; + * + * quantizeAttributes(gltf, options); + */ +function quantizeAttributes(gltf, options) { + var accessors = gltf.accessors; + var meshes = gltf.meshes; + var accessorIds = []; + var attributes; + var attribute; + var meshId; + var primitives; + var primitive; + var i, j; + + // Retrieve the accessors that should be quantized + if (defined(options)) { + attributes = options.attributes; + if (defined(attributes)) { + for (meshId in meshes) { + if (meshes.hasOwnProperty(meshId)) { + var meshAttributes = attributes[meshId]; + if (defined(meshAttributes)) { + primitives = meshes[meshId].primitives; + if (defined(primitives)) { + for (var stringIndex in meshAttributes) { + if (meshAttributes.hasOwnProperty(stringIndex)) { + var attributeArray = meshAttributes[stringIndex]; + var index = parseInt(stringIndex); + primitive = primitives[index]; + if (defined(primitive)) { + for (i = 0; i < attributeArray.length; i++) { + attribute = attributeArray[i]; + var primitiveAttributes = primitive.attributes; + if (defined(primitiveAttributes)) { + var accessor = primitiveAttributes[attribute]; + if (defined(accessor)) { + accessorIds.push(accessor); + } + } + } + } + } + } + } + } + } + } + } + // Quantize all valid attributes + else { + for (meshId in meshes) { + if (meshes.hasOwnProperty(meshId)) { + var mesh = meshes[meshId]; + primitives = mesh.primitives; + if (defined(primitives)) { + for (i = 0; i < primitives.length; i++) { + primitive = primitives[i]; + attributes = primitive.attributes; + if (defined(attributes)) { + for (attribute in attributes) { + if (attributes.hasOwnProperty(attribute)) { + if (attribute === "NORMAL" || attribute === "POSITION") { + accessorIds.push(attributes[attribute]); + } + } + } + } + } + } + } + } + } + } + + // Generate quantized attributes + for (i = 0; i < accessorIds.length; i++) { + var accessorId = accessorIds[i]; + var accessor = accessors[accessorId]; + // as of right now, we only support 32-bit float to 16-bit int quantization + if (accessor.componentType != 5126) { + break; + } + + accessor.extensions = { + "WEB3D_quantized_attributes": {} + }; + attributes = accessor.extensions.WEB3D_quantized_attributes; + var accessorData = getAccessorData(gltf, accessorId); + var min = accessorData.min; + var max = accessorData.max; + + var decodeMatrix = []; + var precision = Math.pow(2, 16) - 1; + for (j = 0; j < min.length + 1; j++) { + for (var k = 0; k < min.length + 1; k++) { + if (j == min.length) { + if (j == k) { + decodeMatrix.push(1); + } + else { + decodeMatrix.push(min[k]); + } + } + else if (j == k) { + decodeMatrix.push((max[j] - min[j])/precision); + } + else { + decodeMatrix.push(0); + } + } + } + attributes.decodeMatrix = decodeMatrix; + + // Quantize the data + var data = accessorData.data; + var encoded = new Uint16Array(data.length); + var index; + for (j = 0; j < data.length; j++) { + index = j % min.length; + encoded[j] = (data[j] - min[index])*precision; + } + + //TODO: Repack the buffers once, don't do it for each accessor + var bufferId = accessorData.bufferId; + var buffer = accessorData.buffer; + var source = buffer.extras._pipeline.source; + var offset = accessorData.byteOffset; + var length = accessorData.byteLength; + var byteSizeDifference = length - encoded.length * 2; + + var output = new Uint8Array(buffer.byteLength - byteSizeDifference); + output.set(source.slice(0, offset)); + output.set(new Uint8Array(encoded.buffer), offset); + output.set(source.slice(offset + length), offset + encoded.length * 2); + + accessor.byteStride = 6; + accessor.componentType = 5123; + + // Overwrite the buffer + gltf.buffers[accessorData.bufferId] = { + type: "arraybuffer", + byteLength: output.length, + extras: { + _pipeline: { + source: output, + deleteExtras: true, + extension: buffer.extras._pipeline.extension + } + } + }; + + // Correct the bufferview + var bufferView = accessorData.bufferView; + bufferView.byteLength -= byteSizeDifference; + + //correct offsets and for any buffer views that use the same buffer + for (var bufferViewId in gltf.bufferViews) { + if (gltf.bufferViews.hasOwnProperty(bufferViewId)) { + bufferView = gltf.bufferViews[bufferViewId]; + if (bufferView.buffer === bufferId) { + if (bufferView.byteOffset > offset) { + bufferView.byteOffset -= byteSizeDifference; + } + } + } + } + + //correct offsets for any accessors that use the same bufferview + for (var otherAccessorId in accessors) { + if (accessors.hasOwnProperty(otherAccessorId)) { + var otherAccessor = accessors[otherAccessorId]; + if (otherAccessor.bufferView === accessor.bufferView) { + if (otherAccessor.byteOffset > accessor.byteOffset) { + otherAccessor.byteOffset -= byteSizeDifference; + } + } + } + } + break; + } +} + +function getAccessorData(gltf, accessorId) { + var accessor = gltf.accessors[accessorId]; + var data = { + min: accessor.min, + max: accessor.max, + data: undefined, + bufferId: undefined, + buffer: undefined, + bufferViewId: undefined, + bufferView: undefined, + byteLength: 0, + byteOffset: 0 + }; + + var byteStride = accessor.byteStride; + if (!defined(byteStride) || byteStride == 0) { + byteStride = 1; + } + var componentByteLength = getByteLengthForComponentType(accessor.componentType); + var numComponents = byteStride/componentByteLength; + data.byteLength = byteStride * accessor.count; + + var bufferViewId = accessor.bufferView; + var bufferView = gltf.bufferViews[bufferViewId]; + data.bufferViewId = bufferViewId; + data.bufferView = bufferView; + data.byteOffset = accessor.byteOffset + bufferView.byteOffset; + + var bufferId = bufferView.buffer; + var buffer = gltf.buffers[bufferId]; + data.bufferId = bufferId; + data.buffer = buffer; + + var source = buffer.extras._pipeline.source; + var typedArray = arrayForComponentType(accessor.componentType, source, data.byteOffset, data.byteLength); + data.data = typedArray; + + data.min = []; + data.max = []; + + for (var i = 0; i < typedArray.length; i++) { + var element = typedArray[i]; + var index = i % numComponents; + if (index >= data.min.length) { + data.min.push(element); + data.max.push(element); + } + else { + if (element < data.min[index]) { + data.min[index] = element; + } + if (element > data.max[index]) { + data.max[index] = element; + } + } + } + accessor.min = data.min; + accessor.max = data.max; + return data; +} + +function getByteLengthForComponentType(type) { + switch (type) { + case 5120: + case 5121: + return 1; + case 5122: + case 5123: + return 2; + case 5126: + return 4; + } +} + +function arrayForComponentType(type, buffer, offset, length) { + var arrayBuffer = undefined; + switch (type) { + case 5120: + return new Int8Array(arrayBuffer); + case 5121: + return new Uint8Array(arrayBuffer); + case 5122: + return new Int16Array(arrayBuffer); + case 5123: + return new Uint16Array(arrayBuffer); + case 5126: + return toFloat32Array(buffer, offset, length); + } +} + +function toFloat32Array(buffer, offset, length) { + var nodeBuffer = new Buffer(buffer); + var array = new Float32Array(length / Float32Array.BYTES_PER_ELEMENT); + var i = 0; + var byteOffset = offset; + while(byteOffset < length + offset) { + array[i] = nodeBuffer.readFloatLE(byteOffset); + byteOffset += Float32Array.BYTES_PER_ELEMENT; + i++ + } + return array; +} \ No newline at end of file diff --git a/specs/data/quantized/Box.bin b/specs/data/quantized/Box.bin new file mode 100644 index 0000000000000000000000000000000000000000..29a29e1385f5c15edd4d0e456c375f61a13c7fd6 GIT binary patch literal 648 zcmb7;0SdxE3 Date: Wed, 1 Jun 2016 13:03:12 -0400 Subject: [PATCH 02/36] A few tweaks --- lib/quantizeAttributes.js | 199 +++++++++++++++++++++----------------- 1 file changed, 111 insertions(+), 88 deletions(-) diff --git a/lib/quantizeAttributes.js b/lib/quantizeAttributes.js index e04aa87d..ec897c05 100644 --- a/lib/quantizeAttributes.js +++ b/lib/quantizeAttributes.js @@ -31,7 +31,6 @@ module.exports = quantizeAttributes; * } * } * }; - * * var options = { * "attributes": { * "geometry": { @@ -39,13 +38,15 @@ module.exports = quantizeAttributes; * } * } * }; - * * quantizeAttributes(gltf, options); + * + * // Quantize and explicitly chunk */ function quantizeAttributes(gltf, options) { var accessors = gltf.accessors; var meshes = gltf.meshes; - var accessorIds = []; + var extensionsUsed = gltf.extensionsUsed; + var accessorIds = {}; var attributes; var attribute; var meshId; @@ -75,7 +76,7 @@ function quantizeAttributes(gltf, options) { if (defined(primitiveAttributes)) { var accessor = primitiveAttributes[attribute]; if (defined(accessor)) { - accessorIds.push(accessor); + accessorIds[accessor] = attribute; } } } @@ -100,8 +101,15 @@ function quantizeAttributes(gltf, options) { if (defined(attributes)) { for (attribute in attributes) { if (attributes.hasOwnProperty(attribute)) { - if (attribute === "NORMAL" || attribute === "POSITION") { - accessorIds.push(attributes[attribute]); + if (attribute.indexOf("NORMAL") >= 0 || + attribute.indexOf("POSITION") >= 0 || + attribute.indexOf("TEXCOORD") >= 0 || + attribute.indexOf("JOINT") >= 0 || + attribute.indexOf("WEIGHT") >= 0 || + attribute.indexOf("COLOR") >= 0 + ) { + var accessor = attributes[attribute]; + accessorIds[accessor] = attribute; } } } @@ -114,111 +122,126 @@ function quantizeAttributes(gltf, options) { } // Generate quantized attributes - for (i = 0; i < accessorIds.length; i++) { - var accessorId = accessorIds[i]; - var accessor = accessors[accessorId]; - // as of right now, we only support 32-bit float to 16-bit int quantization - if (accessor.componentType != 5126) { - break; - } - - accessor.extensions = { - "WEB3D_quantized_attributes": {} - }; - attributes = accessor.extensions.WEB3D_quantized_attributes; - var accessorData = getAccessorData(gltf, accessorId); - var min = accessorData.min; - var max = accessorData.max; + for (var accessorId in accessorIds) { + if (accessorIds.hasOwnProperty(accessorId)) { + var attribute = accessorIds[accessorId]; + var accessor = accessors[accessorId]; + // as of right now, we only support 32-bit float to 16-bit int quantization + if (accessor.componentType != 5126) { + break; + } + accessor.extensions = { + "WEB3D_quantized_attributes": {} + }; + attributes = accessor.extensions.WEB3D_quantized_attributes; + var accessorData = getAccessorData(gltf, accessorId); + var min = accessorData.min; + var max = accessorData.max; - var decodeMatrix = []; - var precision = Math.pow(2, 16) - 1; - for (j = 0; j < min.length + 1; j++) { - for (var k = 0; k < min.length + 1; k++) { - if (j == min.length) { - if (j == k) { - decodeMatrix.push(1); + var decodeMatrix = []; + var precision = Math.pow(2, 16) - 1; + for (j = 0; j < min.length + 1; j++) { + for (var k = 0; k < min.length + 1; k++) { + if (j == min.length) { + if (j == k) { + decodeMatrix.push(1); + } + else { + decodeMatrix.push(min[k]); + } + } + else if (j == k) { + decodeMatrix.push((max[j] - min[j]) / precision); } else { - decodeMatrix.push(min[k]); + decodeMatrix.push(0); } } - else if (j == k) { - decodeMatrix.push((max[j] - min[j])/precision); - } - else { - decodeMatrix.push(0); - } } - } - attributes.decodeMatrix = decodeMatrix; + // decodedMin and decodedMax are required for POSITION attributes + if (attribute === "POSITION") { + attributes.decodedMin = min; + attributes.decodedMax = max; + } + attributes.decodeMatrix = decodeMatrix; - // Quantize the data - var data = accessorData.data; - var encoded = new Uint16Array(data.length); - var index; - for (j = 0; j < data.length; j++) { - index = j % min.length; - encoded[j] = (data[j] - min[index])*precision; - } - //TODO: Repack the buffers once, don't do it for each accessor - var bufferId = accessorData.bufferId; - var buffer = accessorData.buffer; - var source = buffer.extras._pipeline.source; - var offset = accessorData.byteOffset; - var length = accessorData.byteLength; - var byteSizeDifference = length - encoded.length * 2; + // Quantize the data + var data = accessorData.data; + var encoded = new Uint16Array(data.length); + var index; + for (j = 0; j < data.length; j++) { + index = j % min.length; + encoded[j] = (data[j] - min[index]) * precision / (max[index] - min[index]); + } + + //TODO: Repack the buffers once, don't do it for each accessor + //Adjust buffer to be float aligned + var shift = (encoded.length * 2) % 4; + + var bufferId = accessorData.bufferId; + var buffer = accessorData.buffer; + var source = buffer.extras._pipeline.source; + var offset = accessorData.byteOffset; + var length = accessorData.byteLength; + var byteSizeDifference = length - (encoded.length * 2 + shift); - var output = new Uint8Array(buffer.byteLength - byteSizeDifference); - output.set(source.slice(0, offset)); - output.set(new Uint8Array(encoded.buffer), offset); - output.set(source.slice(offset + length), offset + encoded.length * 2); + var output = new Uint8Array(buffer.byteLength - byteSizeDifference); + output.set(source.slice(0, offset)); + output.set(new Uint8Array(encoded.buffer), offset); + output.set(source.slice(offset + length), offset + encoded.length * 2 + shift); - accessor.byteStride = 6; - accessor.componentType = 5123; + accessor.byteStride = accessor.byteStride / 2; + accessor.componentType = 5123; - // Overwrite the buffer - gltf.buffers[accessorData.bufferId] = { - type: "arraybuffer", - byteLength: output.length, - extras: { - _pipeline: { - source: output, - deleteExtras: true, - extension: buffer.extras._pipeline.extension + // Overwrite the buffer + gltf.buffers[accessorData.bufferId] = { + type: "arraybuffer", + byteLength: output.length, + extras: { + _pipeline: { + source: output, + deleteExtras: true, + extension: buffer.extras._pipeline.extension + } } - } - }; + }; - // Correct the bufferview - var bufferView = accessorData.bufferView; - bufferView.byteLength -= byteSizeDifference; + // Correct the bufferview + var bufferView = accessorData.bufferView; + bufferView.byteLength -= byteSizeDifference; - //correct offsets and for any buffer views that use the same buffer - for (var bufferViewId in gltf.bufferViews) { - if (gltf.bufferViews.hasOwnProperty(bufferViewId)) { - bufferView = gltf.bufferViews[bufferViewId]; - if (bufferView.buffer === bufferId) { - if (bufferView.byteOffset > offset) { - bufferView.byteOffset -= byteSizeDifference; + //correct offsets and for any buffer views that use the same buffer + for (var bufferViewId in gltf.bufferViews) { + if (gltf.bufferViews.hasOwnProperty(bufferViewId)) { + bufferView = gltf.bufferViews[bufferViewId]; + if (bufferView.buffer === bufferId) { + if (bufferView.byteOffset > offset) { + bufferView.byteOffset -= byteSizeDifference; + } } } } - } - //correct offsets for any accessors that use the same bufferview - for (var otherAccessorId in accessors) { - if (accessors.hasOwnProperty(otherAccessorId)) { - var otherAccessor = accessors[otherAccessorId]; - if (otherAccessor.bufferView === accessor.bufferView) { - if (otherAccessor.byteOffset > accessor.byteOffset) { - otherAccessor.byteOffset -= byteSizeDifference; + //correct offsets for any accessors that use the same bufferview + for (var otherAccessorId in accessors) { + if (accessors.hasOwnProperty(otherAccessorId)) { + var otherAccessor = accessors[otherAccessorId]; + if (otherAccessor.bufferView === accessor.bufferView) { + if (otherAccessor.byteOffset > accessor.byteOffset) { + otherAccessor.byteOffset -= byteSizeDifference; + } } } } } - break; } + // Add WEB3D_quantized_attributes extension to extensionsUsed + if (!defined(extensionsUsed)) { + extensionsUsed = []; + gltf.extensionsUsed = extensionsUsed; + } + extensionsUsed.push('WEB3D_quantized_attributes'); } function getAccessorData(gltf, accessorId) { From ff81e8f5b819a25251d25fb2191493a7a3012190 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Fri, 3 Jun 2016 13:15:37 -0400 Subject: [PATCH 03/36] Added getBinaryGltf to return buffer --- lib/getBinaryGltf.js | 102 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 lib/getBinaryGltf.js diff --git a/lib/getBinaryGltf.js b/lib/getBinaryGltf.js new file mode 100644 index 00000000..6fff4f99 --- /dev/null +++ b/lib/getBinaryGltf.js @@ -0,0 +1,102 @@ +'use strict'; +var Cesium = require('cesium'); +var defined = Cesium.defined; +var defaultValue = Cesium.defaultValue; +var sizeOf = require('image-size'); +var mime = require('mime'); +var mergeBuffers = require('./mergeBuffers'); +var removePipelineExtras = require('./removePipelineExtras'); + +module.exports = getBinaryGltf; + +function getBinaryGltf(gltf, callback) { + //Create the special binary buffer from the existing buffers + gltf.bufferViews = defaultValue(gltf.bufferViews, {}); + gltf.buffers = defaultValue(gltf.buffers, {}); + mergeBuffers(gltf, 'binary_glTF'); + + var bufferViews = gltf.bufferViews; + var buffers = gltf.buffers; + var currentOffset = buffers.binary_glTF.byteLength; + var body = buffers.binary_glTF.extras._pipeline.source; + var currentBinaryView = 0; + //Update object with KHR_binary_glTF properties and add to body and bufferViews + function updateBinaryObject(name) { + var objects = gltf[name]; + if (defined(objects)) { + for (var objectId in objects) { + if (objects.hasOwnProperty(objectId)) { + var object = objects[objectId]; + + //Update object with binary format + object.uri = "data:,"; + object.extensions = defaultValue(object.extensions, {}); + object.extensions.KHR_binary_glTF = defaultValue(object.extensions.KHR_binary_glTF, {}); + var KHR_binary_glTF = object.extensions.KHR_binary_glTF; + + //Create a bufferView based on the byte length and current offset + var bufferViewKeys = Object.keys(bufferViews); + while (bufferViewKeys.indexOf('binary_bufferView' + currentBinaryView) != -1) { + currentBinaryView++; + } + + var objectSource = object.extras._pipeline.source; + var bufferViewId = 'binary_bufferView' + currentBinaryView; + KHR_binary_glTF.bufferView = bufferViewId; //Create bufferview + bufferViews[bufferViewId] = { + "buffer": "binary_glTF", + "byteLength": objectSource.length, + "byteOffset": currentOffset + }; + currentOffset += objectSource.length; + + //Append the object source to the binary body + body = Buffer.concat([body, objectSource]); + + //Add additional properties for images + if (name === 'images') { + KHR_binary_glTF.mimeType = mime.lookup(object.extras._pipeline.extension); + + var dimensions = sizeOf(object.extras._pipeline.source); + KHR_binary_glTF.width = dimensions.width; + KHR_binary_glTF.height = dimensions.height; + } + } + } + } + } + + updateBinaryObject('shaders'); + updateBinaryObject('images'); + + buffers.binary_glTF.byteLength = currentOffset; + + //Remove extras objects before writing + removePipelineExtras(gltf); + + //Create padded binary scene string and calculate total length + var sceneString = JSON.stringify(gltf); + var sceneLength = Buffer.byteLength(sceneString); + sceneLength += 4 - (sceneLength % 4); + var padding = new Array(sceneLength + 1).join(' '); + sceneString = (sceneString + padding).substring(0, sceneLength); + var bodyOffset = 20 + sceneLength; + var glbLength = bodyOffset + body.length; + + //Write binary glTF header (magic, version, length, sceneLength, sceneFormat) + var header = new Buffer(20); + header.write('glTF', 0); + header.writeUInt32LE(1, 4); + header.writeUInt32LE(glbLength, 8); + header.writeUInt32LE(sceneLength, 12); + header.writeUInt32LE(0, 16); + + //Create scene buffer and overall buffer + var scene = new Buffer(sceneString); + var glb = Buffer.concat([header, scene, body], glbLength); + + if (callback) { + callback(header, scene, body); + } + return glb; +} \ No newline at end of file From f83a5df0e3ffe70e46012f87a02c833b95453645 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Fri, 3 Jun 2016 13:15:54 -0400 Subject: [PATCH 04/36] Made writeBinaryGltf use getBinaryGltf --- lib/writeBinaryGltf.js | 105 ++--------------------------------------- 1 file changed, 3 insertions(+), 102 deletions(-) diff --git a/lib/writeBinaryGltf.js b/lib/writeBinaryGltf.js index 4c6eba2a..30aa3aac 100644 --- a/lib/writeBinaryGltf.js +++ b/lib/writeBinaryGltf.js @@ -2,15 +2,7 @@ var fs = require('fs'); var path = require('path'); var mkdirp = require('mkdirp'); -var Cesium = require('cesium'); -var defined = Cesium.defined; -var defaultValue = Cesium.defaultValue; -var sizeOf = require('image-size'); -var mime = require('mime'); -var objectValues = require('object-values'); -var writeSource = require('./writeSource'); -var mergeBuffers = require('./mergeBuffers'); -var removePipelineExtras = require('./removePipelineExtras'); +var getBinaryGltf = requilre('./getBinaryGltf'); module.exports = writeBinaryGltf; @@ -21,102 +13,11 @@ function writeBinaryGltf(gltf, outputPath, createDirectory, callback) { mkdirp.sync(path.dirname(outputPath)); } - //Create the special binary buffer from the existing buffers - gltf.bufferViews = defaultValue(gltf.bufferViews, {}); - gltf.buffers = defaultValue(gltf.buffers, {}); - mergeBuffers(gltf, 'binary_glTF'); - - var bufferViews = gltf.bufferViews; - var buffers = gltf.buffers; - var currentOffset = buffers.binary_glTF.byteLength; - var body = buffers.binary_glTF.extras._pipeline.source; - var currentBinaryView = 0; - //Update object with KHR_binary_glTF properties and add to body and bufferViews - function updateBinaryObject(name) { - var objects = gltf[name]; - if (defined(objects)) { - for (var objectId in objects) { - if (objects.hasOwnProperty(objectId)) { - var object = objects[objectId]; - - //Update object with binary format - object.uri = "data:,"; - object.extensions = defaultValue(object.extensions, {}); - object.extensions.KHR_binary_glTF = defaultValue(object.extensions.KHR_binary_glTF, {}); - var KHR_binary_glTF = object.extensions.KHR_binary_glTF; - - //Create a bufferView based on the byte length and current offset - var bufferViewKeys = Object.keys(bufferViews); - while (bufferViewKeys.indexOf('binary_bufferView' + currentBinaryView) != -1) { - currentBinaryView++; - } - - var objectSource = object.extras._pipeline.source; - var bufferViewId = 'binary_bufferView' + currentBinaryView; - KHR_binary_glTF.bufferView = bufferViewId; //Create bufferview - bufferViews[bufferViewId] = { - "buffer": "binary_glTF", - "byteLength": objectSource.length, - "byteOffset": currentOffset - }; - currentOffset += objectSource.length; - - //Append the object source to the binary body - body = Buffer.concat([body, objectSource]); - - //Add additional properties for images - if (name === 'images') { - KHR_binary_glTF.mimeType = mime.lookup(object.extras._pipeline.extension); - - var dimensions = sizeOf(object.extras._pipeline.source); - KHR_binary_glTF.width = dimensions.width; - KHR_binary_glTF.height = dimensions.height; - } - } - } - } - } - - updateBinaryObject('shaders'); - updateBinaryObject('images'); - - buffers.binary_glTF.byteLength = currentOffset; - - //Remove extras objects before writing - removePipelineExtras(gltf); - - //Create padded binary scene string and calculate total length - var sceneString = JSON.stringify(gltf); - var sceneLength = Buffer.byteLength(sceneString); - sceneLength += 4 - (sceneLength % 4); - var padding = new Array(sceneLength + 1).join(' '); - sceneString = (sceneString + padding).substring(0, sceneLength); - var bodyOffset = 20 + sceneLength; - var glbLength = bodyOffset + body.length; - - //Write binary glTF header (magic, version, length, sceneLength, sceneFormat) - var header = new Buffer(20); - header.write('glTF', 0); - header.writeUInt32LE(1, 4); - header.writeUInt32LE(glbLength, 8); - header.writeUInt32LE(sceneLength, 12); - header.writeUInt32LE(0, 16); - - //Create scene buffer and overall buffer - var scene = new Buffer(sceneString); - var glb = Buffer.concat([header, scene, body], glbLength); + var glb = getBinaryGltf(gltf, callback); fs.writeFile(outputPath, glb, function (err) { if (err) { - if (callback) { - callback(err); - } - else{ - throw err; - } - } - if (callback) { - callback(header, scene, body); + throw err; } }); } \ No newline at end of file From 21ab7a63dc6b0cf32f2aee8035bd0ebec0f88bd4 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Fri, 3 Jun 2016 13:20:59 -0400 Subject: [PATCH 05/36] Fixed some jsHint errors --- lib/quantizeAttributes.js | 68 +++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/lib/quantizeAttributes.js b/lib/quantizeAttributes.js index ec897c05..da88c911 100644 --- a/lib/quantizeAttributes.js +++ b/lib/quantizeAttributes.js @@ -39,8 +39,6 @@ module.exports = quantizeAttributes; * } * }; * quantizeAttributes(gltf, options); - * - * // Quantize and explicitly chunk */ function quantizeAttributes(gltf, options) { var accessors = gltf.accessors; @@ -49,15 +47,19 @@ function quantizeAttributes(gltf, options) { var accessorIds = {}; var attributes; var attribute; + var accessor; var meshId; var primitives; var primitive; var i, j; + var index; // Retrieve the accessors that should be quantized + var quantizeAll = true; if (defined(options)) { attributes = options.attributes; if (defined(attributes)) { + quantizeAll = false; for (meshId in meshes) { if (meshes.hasOwnProperty(meshId)) { var meshAttributes = attributes[meshId]; @@ -67,14 +69,14 @@ function quantizeAttributes(gltf, options) { for (var stringIndex in meshAttributes) { if (meshAttributes.hasOwnProperty(stringIndex)) { var attributeArray = meshAttributes[stringIndex]; - var index = parseInt(stringIndex); + index = parseInt(stringIndex); primitive = primitives[index]; if (defined(primitive)) { for (i = 0; i < attributeArray.length; i++) { attribute = attributeArray[i]; var primitiveAttributes = primitive.attributes; if (defined(primitiveAttributes)) { - var accessor = primitiveAttributes[attribute]; + accessor = primitiveAttributes[attribute]; if (defined(accessor)) { accessorIds[accessor] = attribute; } @@ -88,29 +90,29 @@ function quantizeAttributes(gltf, options) { } } } - // Quantize all valid attributes - else { - for (meshId in meshes) { - if (meshes.hasOwnProperty(meshId)) { - var mesh = meshes[meshId]; - primitives = mesh.primitives; - if (defined(primitives)) { - for (i = 0; i < primitives.length; i++) { - primitive = primitives[i]; - attributes = primitive.attributes; - if (defined(attributes)) { - for (attribute in attributes) { - if (attributes.hasOwnProperty(attribute)) { - if (attribute.indexOf("NORMAL") >= 0 || - attribute.indexOf("POSITION") >= 0 || - attribute.indexOf("TEXCOORD") >= 0 || - attribute.indexOf("JOINT") >= 0 || - attribute.indexOf("WEIGHT") >= 0 || - attribute.indexOf("COLOR") >= 0 - ) { - var accessor = attributes[attribute]; - accessorIds[accessor] = attribute; - } + } + // Quantize all valid attributes + if (quantizeAll) { + for (meshId in meshes) { + if (meshes.hasOwnProperty(meshId)) { + var mesh = meshes[meshId]; + primitives = mesh.primitives; + if (defined(primitives)) { + for (i = 0; i < primitives.length; i++) { + primitive = primitives[i]; + attributes = primitive.attributes; + if (defined(attributes)) { + for (attribute in attributes) { + if (attributes.hasOwnProperty(attribute)) { + if (attribute.indexOf("NORMAL") >= 0 || + attribute.indexOf("POSITION") >= 0 || + attribute.indexOf("TEXCOORD") >= 0 || + attribute.indexOf("JOINT") >= 0 || + attribute.indexOf("WEIGHT") >= 0 || + attribute.indexOf("COLOR") >= 0 + ) { + accessor = attributes[attribute]; + accessorIds[accessor] = attribute; } } } @@ -124,8 +126,8 @@ function quantizeAttributes(gltf, options) { // Generate quantized attributes for (var accessorId in accessorIds) { if (accessorIds.hasOwnProperty(accessorId)) { - var attribute = accessorIds[accessorId]; - var accessor = accessors[accessorId]; + attribute = accessorIds[accessorId]; + accessor = accessors[accessorId]; // as of right now, we only support 32-bit float to 16-bit int quantization if (accessor.componentType != 5126) { break; @@ -259,7 +261,7 @@ function getAccessorData(gltf, accessorId) { }; var byteStride = accessor.byteStride; - if (!defined(byteStride) || byteStride == 0) { + if (!defined(byteStride) || byteStride === 0) { byteStride = 1; } var componentByteLength = getByteLengthForComponentType(accessor.componentType); @@ -300,8 +302,6 @@ function getAccessorData(gltf, accessorId) { } } } - accessor.min = data.min; - accessor.max = data.max; return data; } @@ -319,7 +319,7 @@ function getByteLengthForComponentType(type) { } function arrayForComponentType(type, buffer, offset, length) { - var arrayBuffer = undefined; + var arrayBuffer; switch (type) { case 5120: return new Int8Array(arrayBuffer); @@ -342,7 +342,7 @@ function toFloat32Array(buffer, offset, length) { while(byteOffset < length + offset) { array[i] = nodeBuffer.readFloatLE(byteOffset); byteOffset += Float32Array.BYTES_PER_ELEMENT; - i++ + i++; } return array; } \ No newline at end of file From 8588875ffd8a392b45082eb12560b0855f166fe8 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Mon, 6 Jun 2016 11:38:04 -0400 Subject: [PATCH 06/36] Split updateBinaryObject out into its own function --- lib/getBinaryGltf.js | 96 ++++++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/lib/getBinaryGltf.js b/lib/getBinaryGltf.js index 6fff4f99..c8210f08 100644 --- a/lib/getBinaryGltf.js +++ b/lib/getBinaryGltf.js @@ -9,6 +9,53 @@ var removePipelineExtras = require('./removePipelineExtras'); module.exports = getBinaryGltf; +//Update object with KHR_binary_glTF properties and add to body and bufferViews +function updateBinaryObject(gltf, body, name, offset) { + var objects = gltf[name]; + if (defined(objects)) { + for (var objectId in objects) { + if (objects.hasOwnProperty(objectId)) { + var object = objects[objectId]; + + //Update object with binary format + object.uri = 'data:,'; + object.extensions = defaultValue(object.extensions, {}); + object.extensions.KHR_binary_glTF = defaultValue(object.extensions.KHR_binary_glTF, {}); + var KHR_binary_glTF = object.extensions.KHR_binary_glTF; + + //Create a bufferView based on the byte length and current offset + var bufferViewKeys = Object.keys(bufferViews); + while (bufferViewKeys.indexOf('binary_bufferView' + currentBinaryView) != -1) { + currentBinaryView++; + } + + var objectSource = object.extras._pipeline.source; + var bufferViewId = 'binary_bufferView' + currentBinaryView; + KHR_binary_glTF.bufferView = bufferViewId; //Create bufferview + bufferViews[bufferViewId] = { + buffer : 'binary_glTF', + byteLength : objectSource.length, + byteOffset : currentOffset + }; + offset += objectSource.length; + + //Append the object source to the binary body + body = Buffer.concat([body, objectSource]); + + //Add additional properties for images + if (name === 'images') { + KHR_binary_glTF.mimeType = mime.lookup(object.extras._pipeline.extension); + + var dimensions = sizeOf(object.extras._pipeline.source); + KHR_binary_glTF.width = dimensions.width; + KHR_binary_glTF.height = dimensions.height; + } + } + } + } + return offset; +} + function getBinaryGltf(gltf, callback) { //Create the special binary buffer from the existing buffers gltf.bufferViews = defaultValue(gltf.bufferViews, {}); @@ -20,54 +67,9 @@ function getBinaryGltf(gltf, callback) { var currentOffset = buffers.binary_glTF.byteLength; var body = buffers.binary_glTF.extras._pipeline.source; var currentBinaryView = 0; - //Update object with KHR_binary_glTF properties and add to body and bufferViews - function updateBinaryObject(name) { - var objects = gltf[name]; - if (defined(objects)) { - for (var objectId in objects) { - if (objects.hasOwnProperty(objectId)) { - var object = objects[objectId]; - - //Update object with binary format - object.uri = "data:,"; - object.extensions = defaultValue(object.extensions, {}); - object.extensions.KHR_binary_glTF = defaultValue(object.extensions.KHR_binary_glTF, {}); - var KHR_binary_glTF = object.extensions.KHR_binary_glTF; - - //Create a bufferView based on the byte length and current offset - var bufferViewKeys = Object.keys(bufferViews); - while (bufferViewKeys.indexOf('binary_bufferView' + currentBinaryView) != -1) { - currentBinaryView++; - } - - var objectSource = object.extras._pipeline.source; - var bufferViewId = 'binary_bufferView' + currentBinaryView; - KHR_binary_glTF.bufferView = bufferViewId; //Create bufferview - bufferViews[bufferViewId] = { - "buffer": "binary_glTF", - "byteLength": objectSource.length, - "byteOffset": currentOffset - }; - currentOffset += objectSource.length; - - //Append the object source to the binary body - body = Buffer.concat([body, objectSource]); - - //Add additional properties for images - if (name === 'images') { - KHR_binary_glTF.mimeType = mime.lookup(object.extras._pipeline.extension); - - var dimensions = sizeOf(object.extras._pipeline.source); - KHR_binary_glTF.width = dimensions.width; - KHR_binary_glTF.height = dimensions.height; - } - } - } - } - } - updateBinaryObject('shaders'); - updateBinaryObject('images'); + currentOffset = updateBinaryObject(gltf, body, 'shaders', currentOffset); + currentOffset = updateBinaryObject(gltf, body, 'images', currentOffset); buffers.binary_glTF.byteLength = currentOffset; From 4b83b4dd3301c64698894cd8b3703ff62bcdd801 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Mon, 6 Jun 2016 11:40:05 -0400 Subject: [PATCH 07/36] Body referenced form pipelineExtras --- lib/getBinaryGltf.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/getBinaryGltf.js b/lib/getBinaryGltf.js index c8210f08..3e3f0fba 100644 --- a/lib/getBinaryGltf.js +++ b/lib/getBinaryGltf.js @@ -10,7 +10,7 @@ var removePipelineExtras = require('./removePipelineExtras'); module.exports = getBinaryGltf; //Update object with KHR_binary_glTF properties and add to body and bufferViews -function updateBinaryObject(gltf, body, name, offset) { +function updateBinaryObject(gltf, pipelineExtras, name, offset) { var objects = gltf[name]; if (defined(objects)) { for (var objectId in objects) { @@ -40,7 +40,7 @@ function updateBinaryObject(gltf, body, name, offset) { offset += objectSource.length; //Append the object source to the binary body - body = Buffer.concat([body, objectSource]); + pipelineExtras.source = Buffer.concat([pipelineExtras.source, objectSource]); //Add additional properties for images if (name === 'images') { @@ -65,12 +65,13 @@ function getBinaryGltf(gltf, callback) { var bufferViews = gltf.bufferViews; var buffers = gltf.buffers; var currentOffset = buffers.binary_glTF.byteLength; - var body = buffers.binary_glTF.extras._pipeline.source; + var pipelineExtras = buffers.binarya_glTF.extras._pipeline; var currentBinaryView = 0; - currentOffset = updateBinaryObject(gltf, body, 'shaders', currentOffset); - currentOffset = updateBinaryObject(gltf, body, 'images', currentOffset); + currentOffset = updateBinaryObject(gltf, pipelineExtras, 'shaders', currentOffset); + currentOffset = updateBinaryObject(gltf, pipelineExtras, 'images', currentOffset); + var body = buffers.binary_glTF.extras._pipeline.source; buffers.binary_glTF.byteLength = currentOffset; //Remove extras objects before writing From 0f6672009ca6356b082d0615e2d1bcf1520769b6 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Mon, 6 Jun 2016 11:44:23 -0400 Subject: [PATCH 08/36] Split getQuantizableAttributes into its own function --- lib/quantizeAttributes.js | 139 ++++++++++++++++++++++---------------- 1 file changed, 82 insertions(+), 57 deletions(-) diff --git a/lib/quantizeAttributes.js b/lib/quantizeAttributes.js index da88c911..bafae522 100644 --- a/lib/quantizeAttributes.js +++ b/lib/quantizeAttributes.js @@ -60,67 +60,12 @@ function quantizeAttributes(gltf, options) { attributes = options.attributes; if (defined(attributes)) { quantizeAll = false; - for (meshId in meshes) { - if (meshes.hasOwnProperty(meshId)) { - var meshAttributes = attributes[meshId]; - if (defined(meshAttributes)) { - primitives = meshes[meshId].primitives; - if (defined(primitives)) { - for (var stringIndex in meshAttributes) { - if (meshAttributes.hasOwnProperty(stringIndex)) { - var attributeArray = meshAttributes[stringIndex]; - index = parseInt(stringIndex); - primitive = primitives[index]; - if (defined(primitive)) { - for (i = 0; i < attributeArray.length; i++) { - attribute = attributeArray[i]; - var primitiveAttributes = primitive.attributes; - if (defined(primitiveAttributes)) { - accessor = primitiveAttributes[attribute]; - if (defined(accessor)) { - accessorIds[accessor] = attribute; - } - } - } - } - } - } - } - } - } - } + accessorIds = getQuantizableAttributes(gltf, attributes); } } // Quantize all valid attributes if (quantizeAll) { - for (meshId in meshes) { - if (meshes.hasOwnProperty(meshId)) { - var mesh = meshes[meshId]; - primitives = mesh.primitives; - if (defined(primitives)) { - for (i = 0; i < primitives.length; i++) { - primitive = primitives[i]; - attributes = primitive.attributes; - if (defined(attributes)) { - for (attribute in attributes) { - if (attributes.hasOwnProperty(attribute)) { - if (attribute.indexOf("NORMAL") >= 0 || - attribute.indexOf("POSITION") >= 0 || - attribute.indexOf("TEXCOORD") >= 0 || - attribute.indexOf("JOINT") >= 0 || - attribute.indexOf("WEIGHT") >= 0 || - attribute.indexOf("COLOR") >= 0 - ) { - accessor = attributes[attribute]; - accessorIds[accessor] = attribute; - } - } - } - } - } - } - } - } + accessorIds = getAllQuantizableAttributes(gltf); } // Generate quantized attributes @@ -160,6 +105,21 @@ function quantizeAttributes(gltf, options) { } } } + // change matrix precision to save space + /*var precision = 1e12; + for (j = 0; j < decodeMatrix.length; j++) { + var value = decodeMatrix[j]; + decodeMatrix[j] = Math.floor(value * precision)/precision; + } + for (j = 0; j < min.length; j++) { + var value = min[j]; + min[j] = Math.floor(value * precision)/precision; + } + for (j = 0; j < max.length; j++) { + var value = max[j]; + max[j] = Math.floor(value * precision)/precision; + }*/ + // decodedMin and decodedMax are required for POSITION attributes if (attribute === "POSITION") { attributes.decodedMin = min; @@ -246,6 +206,71 @@ function quantizeAttributes(gltf, options) { extensionsUsed.push('WEB3D_quantized_attributes'); } +function getQuantizableAttributes(gltf, attributes) { + var accessorIds = []; + for (var meshId in meshes) { + if (meshes.hasOwnProperty(meshId)) { + var meshAttributes = attributes[meshId]; + if (defined(meshAttributes)) { + var primitives = meshes[meshId].primitives; + if (defined(primitives)) { + for (var stringIndex in meshAttributes) { + if (meshAttributes.hasOwnProperty(stringIndex)) { + var attributeArray = meshAttributes[stringIndex]; + index = parseInt(stringIndex); + var primitive = primitives[index]; + if (defined(primitive)) { + for (i = 0; i < attributeArray.length; i++) { + var attribute = attributeArray[i]; + var primitiveAttributes = primitive.attributes; + if (defined(primitiveAttributes)) { + var accessor = primitiveAttributes[attribute]; + if (defined(accessor)) { + accessorIds[accessor] = attribute; + } + } + } + } + } + } + } + } + } + } + return accessorIds; +} + +function getAllQuantizableAttributes(gltf) { + for (var meshId in meshes) { + if (meshes.hasOwnProperty(meshId)) { + var mesh = meshes[meshId]; + var primitives = mesh.primitives; + if (defined(primitives)) { + for (i = 0; i < primitives.length; i++) { + var primitive = primitives[i]; + var attributes = primitive.attributes; + if (defined(attributes)) { + for (var attribute in attributes) { + if (attributes.hasOwnProperty(attribute)) { + if (attribute.indexOf("NORMAL") >= 0 || + attribute.indexOf("POSITION") >= 0 || + attribute.indexOf("TEXCOORD") >= 0 || + attribute.indexOf("JOINT") >= 0 || + attribute.indexOf("WEIGHT") >= 0 || + attribute.indexOf("COLOR") >= 0 + ) { + var accessor = attributes[attribute]; + accessorIds[accessor] = attribute; + } + } + } + } + } + } + } + } +} + function getAccessorData(gltf, accessorId) { var accessor = gltf.accessors[accessorId]; var data = { From 8eaedabaffa0d2d82d08024a3ee7df321570185a Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Mon, 6 Jun 2016 11:52:11 -0400 Subject: [PATCH 09/36] Added addExtensionsUsed utility --- lib/addExtensionsUsed.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 lib/addExtensionsUsed.js diff --git a/lib/addExtensionsUsed.js b/lib/addExtensionsUsed.js new file mode 100644 index 00000000..30f91609 --- /dev/null +++ b/lib/addExtensionsUsed.js @@ -0,0 +1,16 @@ +'use strict'; +var Cesium = require('cesium'); +var defined = Cesium.defined; + +module.exports = addExtensionsUsed; + +function addExtensionsUsed(gltf, extension) { + var extensionsUsed = gltf.extensionsUsed; + if (!defined(extensionsUsed)) { + extensionsUsed = []; + gltf.extensionsUsed = extensionsUsed; + } + if (extensionsUsed.indexOf(extension) < 0) { + extensionsUsed.push(extension); + } +} \ No newline at end of file From 36e5e0787a9e0a6e67bfd65d5717de17c80fd697 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Tue, 7 Jun 2016 10:57:04 -0400 Subject: [PATCH 10/36] Lots of cleanup for quantizeAttributes --- lib/byteLengthForComponentType.js | 28 ++ lib/getAccessorByteStride.js | 21 ++ lib/numberOfComponentsForType.js | 35 +++ lib/quantizeAttributes.js | 416 ++++++++++++------------------ 4 files changed, 246 insertions(+), 254 deletions(-) create mode 100644 lib/byteLengthForComponentType.js create mode 100644 lib/getAccessorByteStride.js create mode 100644 lib/numberOfComponentsForType.js diff --git a/lib/byteLengthForComponentType.js b/lib/byteLengthForComponentType.js new file mode 100644 index 00000000..dd4c6106 --- /dev/null +++ b/lib/byteLengthForComponentType.js @@ -0,0 +1,28 @@ +'use strict' + +module.exports = byteLengthForComponentType; + +/** + * Utility function for retrieving the byte length of a component type. + * As per the spec: + * 5120 (BYTE) : 1 + * 5121 (UNSIGNED_BYTE) : 1 + * 5122 (SHORT) : 2 + * 5123 (UNSIGNED_SHORT) : 2 + * 5126 (FLOAT) : 4 + * + * @param {Number} [componentType] + * @returns {Number} The byte length of the component type. + */ +function byteLengthForComponentType(componentType) { + switch (componentType) { + case 5120: + case 5121: + return 1; + case 5122: + case 5123: + return 2; + case 5126: + return 4 + } +} \ No newline at end of file diff --git a/lib/getAccessorByteStride.js b/lib/getAccessorByteStride.js new file mode 100644 index 00000000..f4f55e79 --- /dev/null +++ b/lib/getAccessorByteStride.js @@ -0,0 +1,21 @@ +'use strict'; +var byteLengthForComponentType = require('./byteLengthForComponentType'); +var numberOfComponentsForType = require('./numberOfComponentsForType'); + +module.exports = getAccessorByteStride; + +/** + * Returns the byte stride of the provided accessor. + * If the byteStride is 0, it is calculated based on type and componentType + * + * @param {Object} [accessor] The accessor. + * @returns {Number} The byte stride of the accessor. + */ +function getAccessorByteStride(accessor) { + if (accessor.byteStride > 0) { + return accessor.byteStride; + } + else { + return byteLengthForComponentType(accessor.componentType) * numberOfComponentsForType(accessor.type); + } +} \ No newline at end of file diff --git a/lib/numberOfComponentsForType.js b/lib/numberOfComponentsForType.js new file mode 100644 index 00000000..d892bb85 --- /dev/null +++ b/lib/numberOfComponentsForType.js @@ -0,0 +1,35 @@ +'use strict'; + +module.exports = numberOfComponentsForType + +/** + * Utility function for retrieving the number of components in a given type. + * As per the spec: + * 'SCALAR' : 1 + * 'VEC2' : 2 + * 'VEC3' : 3 + * 'VEC4' : 4 + * 'MAT2' : 4 + * 'MAT3' : 9 + * 'MAT4' : 16 + * + * @param {String} type + * @returns {Number} + */ +function numberOfComponentsForType(type) { + switch (type) { + case 'SCALAR': + return 1; + case 'VEC2': + return 2; + case 'VEC3': + return 3; + case 'VEC4': + case 'MAT2': + return 4; + case 'MAT3': + return 9; + case 'MAT4': + return 16 + } +} \ No newline at end of file diff --git a/lib/quantizeAttributes.js b/lib/quantizeAttributes.js index bafae522..f6c9e97a 100644 --- a/lib/quantizeAttributes.js +++ b/lib/quantizeAttributes.js @@ -1,58 +1,78 @@ 'use strict'; var Cesium = require('cesium'); + var defined = Cesium.defined; + +var addExtensionsUsed = require('./addExtensionsUsed'); +var byteLengthForComponentType = require('./byteLengthForComponentType'); +var getAccessorByteStride = require('./getAccessorByteStride'); +var numberOfComponentsForType = require('./numberOfComponentsForType'); +var removeUnusedVertices = require('./removeUnusedVertices'); + module.exports = quantizeAttributes; /** + * Quantizes attributes in the provided glTF using the WEB3D_quantized_attributes extension. + * If options is undefined, all quantizable attributes will be quantized with the default options. + * If options.attributes is undefined, all quantizable attributes will be quantized with the provided options. + * If options.precision is undefined, the decodeMatrix be written in its full form. * - * @param gltf - * @param options + * @param {Object} [gltf] A javascript object holding a glTF hierarchy. + * @param {Object} [options=undefined] Defines more specific quantization behavior. + * @param {Object} [options.attributes=undefined] Defines specific attributes to be quantized. This should be as arranged as meshId -> primitiveIndex -> [semantic_0, semantic_1, ... semantic_N] + * @param {Number} [options.precision=undefined] Restricts the number of decimal places in the decodeMatrix. * * @returns glTF with quantized attributes * * @example - * // Quantize the positions of a simple mesh * var gltf = { - * "accessors": { - * "accessor_0": {...} - * "accessor_1": {...} + * accessors: { + * accessor_0: {...} + * accessor_1: {...} * }, - * "meshes": { - * "geometry": { - * "name": "Mesh", - * "primitives": [ + * meshes: { + * geometry: { + * name: 'Mesh', + * primitives: [ * { - * "attributes": { - * "NORMAL": "accessor_0", - * "POSITION": "accessor_1" + * attributes: { + * NORMAL: 'accessor_0', + * POSITION: 'accessor_1' * } * } * ] * } * } * }; - * var options = { - * "attributes": { - * "geometry": { - * "0": ["POSITION"] + * + * // Quantize all valid attributes + * quantizeAttributes(gltf); + * + * // Quantize all valid attributes, restricting the decodeMatrix to 6 decimal places + * quantizeAttributes(gltf, { + * precision: 6 + * } + * + * // Quantize just the positions of a specific mesh + * quantizeAttributes(gltf, { + * attributes: { + * geometry: { + * 0: ['POSITION'] * } * } - * }; - * quantizeAttributes(gltf, options); + * }); */ function quantizeAttributes(gltf, options) { var accessors = gltf.accessors; - var meshes = gltf.meshes; - var extensionsUsed = gltf.extensionsUsed; - var accessorIds = {}; - var attributes; - var attribute; - var accessor; - var meshId; - var primitives; - var primitive; + var bufferViews = gltf.bufferViews; + var buffers = gltf.buffers; var i, j; - var index; + var accessorIds = {}; + var precision = undefined; + if (defined(options.precision)) { + precision = 10^options.precision; + } + var range = Math.pow(2, 16) - 1; // Retrieve the accessors that should be quantized var quantizeAll = true; @@ -68,146 +88,104 @@ function quantizeAttributes(gltf, options) { accessorIds = getAllQuantizableAttributes(gltf); } - // Generate quantized attributes + // Generate quantized attributes and table for repacking the buffer + var isQuantized = false; for (var accessorId in accessorIds) { if (accessorIds.hasOwnProperty(accessorId)) { - attribute = accessorIds[accessorId]; - accessor = accessors[accessorId]; - // as of right now, we only support 32-bit float to 16-bit int quantization - if (accessor.componentType != 5126) { - break; - } + var accessor = accessors[accessorId]; + var bufferViewId = accessor.bufferView; + var bufferView = bufferViews[bufferViewId]; + var bufferId = bufferView.buffer; + var buffer = buffers[bufferId]; accessor.extensions = { "WEB3D_quantized_attributes": {} }; - attributes = accessor.extensions.WEB3D_quantized_attributes; - var accessorData = getAccessorData(gltf, accessorId); - var min = accessorData.min; - var max = accessorData.max; + var attributes = accessor.extensions.WEB3D_quantized_attributes; + // accessor min and max are the extremes of the range + var min = accessor.min; + var max = accessor.max; + accessor.min = Array(min.length).fill(0); + accessor.max = Array(max.length).fill(range); + var decodeMatrix = createDecodeMatrix(min, max, range); - var decodeMatrix = []; - var precision = Math.pow(2, 16) - 1; - for (j = 0; j < min.length + 1; j++) { - for (var k = 0; k < min.length + 1; k++) { - if (j == min.length) { - if (j == k) { - decodeMatrix.push(1); - } - else { - decodeMatrix.push(min[k]); - } - } - else if (j == k) { - decodeMatrix.push((max[j] - min[j]) / precision); - } - else { - decodeMatrix.push(0); - } - } - } // change matrix precision to save space - /*var precision = 1e12; - for (j = 0; j < decodeMatrix.length; j++) { - var value = decodeMatrix[j]; - decodeMatrix[j] = Math.floor(value * precision)/precision; - } - for (j = 0; j < min.length; j++) { - var value = min[j]; - min[j] = Math.floor(value * precision)/precision; - } - for (j = 0; j < max.length; j++) { - var value = max[j]; - max[j] = Math.floor(value * precision)/precision; - }*/ - - // decodedMin and decodedMax are required for POSITION attributes - if (attribute === "POSITION") { - attributes.decodedMin = min; - attributes.decodedMax = max; + if (defined(precision)) { + for (i = 0; i < decodeMatrix.length; i++) { + var value = decodeMatrix[i]; + decodeMatrix[i] = Math.floor(value * precision) / precision; + } } + attributes.decodedMin = min; + attributes.decodedMax = max; attributes.decodeMatrix = decodeMatrix; - - // Quantize the data - var data = accessorData.data; - var encoded = new Uint16Array(data.length); - var index; - for (j = 0; j < data.length; j++) { - index = j % min.length; - encoded[j] = (data[j] - min[index]) * precision / (max[index] - min[index]); - } - - //TODO: Repack the buffers once, don't do it for each accessor - //Adjust buffer to be float aligned - var shift = (encoded.length * 2) % 4; - - var bufferId = accessorData.bufferId; - var buffer = accessorData.buffer; + // Quantize the data in place var source = buffer.extras._pipeline.source; - var offset = accessorData.byteOffset; - var length = accessorData.byteLength; - var byteSizeDifference = length - (encoded.length * 2 + shift); - - var output = new Uint8Array(buffer.byteLength - byteSizeDifference); - output.set(source.slice(0, offset)); - output.set(new Uint8Array(encoded.buffer), offset); - output.set(source.slice(offset + length), offset + encoded.length * 2 + shift); - - accessor.byteStride = accessor.byteStride / 2; - accessor.componentType = 5123; - - // Overwrite the buffer - gltf.buffers[accessorData.bufferId] = { - type: "arraybuffer", - byteLength: output.length, - extras: { - _pipeline: { - source: output, - deleteExtras: true, - extension: buffer.extras._pipeline.extension - } - } - }; - - // Correct the bufferview - var bufferView = accessorData.bufferView; - bufferView.byteLength -= byteSizeDifference; - - //correct offsets and for any buffer views that use the same buffer - for (var bufferViewId in gltf.bufferViews) { - if (gltf.bufferViews.hasOwnProperty(bufferViewId)) { - bufferView = gltf.bufferViews[bufferViewId]; - if (bufferView.buffer === bufferId) { - if (bufferView.byteOffset > offset) { - bufferView.byteOffset -= byteSizeDifference; - } - } - } - } - - //correct offsets for any accessors that use the same bufferview - for (var otherAccessorId in accessors) { - if (accessors.hasOwnProperty(otherAccessorId)) { - var otherAccessor = accessors[otherAccessorId]; - if (otherAccessor.bufferView === accessor.bufferView) { - if (otherAccessor.byteOffset > accessor.byteOffset) { - otherAccessor.byteOffset -= byteSizeDifference; - } - } + var offset = accessor.byteOffset + bufferView.byteOffset; + var num = 0; + var count = accessor.count; + var byteStride = getAccessorByteStride(accessor); + accessor.byteStride = byteStride; + var componentByteLength = byteLengthForComponentType(accessor.componentType); + var numberOfComponents = numberOfComponentsForType(accessor.type); + for (i = offset; num < count; i+=byteStride) { + for (var j = 0; j < numberOfComponents; j++) { + var value = source.readFloatLE(i + j * componentByteLength); + var encoded = Math.round((value - min[j]) * range / (max[j] - min[j])); + source.writeUInt16LE(encoded, i + j * 2); } + num++; } + accessor.componentType = 5123; + isQuantized = true; } } - // Add WEB3D_quantized_attributes extension to extensionsUsed - if (!defined(extensionsUsed)) { - extensionsUsed = []; - gltf.extensionsUsed = extensionsUsed; + if (isQuantized) { + // Repack the buffers + removeUnusedVertices(gltf); + // Finalize + addExtensionsUsed(gltf, 'WEB3D_quantized_attributes'); + } +} + +function createDecodeMatrix(min, max, range) { + var size = min.length + 1; + if (size === 3) { + return createDecodeMatrix3(min, max, range); + } else if (size === 4) { + return createDecodeMatrix4(min, max, range); + } else if (size === 5) { + return createDecodeMatrix5(min, max, range); } - extensionsUsed.push('WEB3D_quantized_attributes'); +} + +function createDecodeMatrix3(min, max, range) { + return [(max[0] - min[0])/range, 0, 0, + 0, (max[1] - min[1])/range, 0, + min[0], min[1], 1 + ]; +} + +function createDecodeMatrix4(min, max, range) { + return [(max[0] - min[0])/range, 0, 0, 0, + 0, (max[1] - min[1])/range, 0, 0, + 0, 0, (max[2] - min[2])/range, 0, + min[0], min[1], min[2], 1 + ]; +} + +function createDecodeMatrix5(min, max, range) { + return [(max[0] - min[0])/range, 0, 0, 0, 0, + 0, (max[1] - min[1])/range, 0, 0, 0, + 0, 0, (max[2] - min[2])/range, 0, 0, + 0, 0, 0, (max[3] - min[3])/range, 0, + min[0], min[1], min[2], min[3], 1 + ]; } function getQuantizableAttributes(gltf, attributes) { - var accessorIds = []; + var meshes = gltf.meshes; + var accessorIds = {}; for (var meshId in meshes) { if (meshes.hasOwnProperty(meshId)) { var meshAttributes = attributes[meshId]; @@ -217,10 +195,10 @@ function getQuantizableAttributes(gltf, attributes) { for (var stringIndex in meshAttributes) { if (meshAttributes.hasOwnProperty(stringIndex)) { var attributeArray = meshAttributes[stringIndex]; - index = parseInt(stringIndex); + var index = parseInt(stringIndex); var primitive = primitives[index]; if (defined(primitive)) { - for (i = 0; i < attributeArray.length; i++) { + for (var i = 0; i < attributeArray.length; i++) { var attribute = attributeArray[i]; var primitiveAttributes = primitive.attributes; if (defined(primitiveAttributes)) { @@ -241,6 +219,10 @@ function getQuantizableAttributes(gltf, attributes) { } function getAllQuantizableAttributes(gltf) { + var accessors = gltf.accessors; + var meshes = gltf.meshes; + var visitedAccessors = {}; + var accessorAttributes = {}; for (var meshId in meshes) { if (meshes.hasOwnProperty(meshId)) { var mesh = meshes[meshId]; @@ -252,15 +234,15 @@ function getAllQuantizableAttributes(gltf) { if (defined(attributes)) { for (var attribute in attributes) { if (attributes.hasOwnProperty(attribute)) { - if (attribute.indexOf("NORMAL") >= 0 || - attribute.indexOf("POSITION") >= 0 || - attribute.indexOf("TEXCOORD") >= 0 || - attribute.indexOf("JOINT") >= 0 || - attribute.indexOf("WEIGHT") >= 0 || - attribute.indexOf("COLOR") >= 0 - ) { - var accessor = attributes[attribute]; - accessorIds[accessor] = attribute; + if (attributeIsQuantizable(attribute)) { + var accessorId = attributes[attribute]; + if (defined(visitedAccessors[accessorId])) { + var accessor = accessors[accessorId]; + if (accessorIsQuantizable(accessor)) { + accessorAttributes[accessor] = attribute; + } + visitedAccessors[accessorId] = true; + } } } } @@ -269,105 +251,31 @@ function getAllQuantizableAttributes(gltf) { } } } + return accessorAttributes; } -function getAccessorData(gltf, accessorId) { - var accessor = gltf.accessors[accessorId]; - var data = { - min: accessor.min, - max: accessor.max, - data: undefined, - bufferId: undefined, - buffer: undefined, - bufferViewId: undefined, - bufferView: undefined, - byteLength: 0, - byteOffset: 0 - }; - - var byteStride = accessor.byteStride; - if (!defined(byteStride) || byteStride === 0) { - byteStride = 1; - } - var componentByteLength = getByteLengthForComponentType(accessor.componentType); - var numComponents = byteStride/componentByteLength; - data.byteLength = byteStride * accessor.count; - - var bufferViewId = accessor.bufferView; - var bufferView = gltf.bufferViews[bufferViewId]; - data.bufferViewId = bufferViewId; - data.bufferView = bufferView; - data.byteOffset = accessor.byteOffset + bufferView.byteOffset; - - var bufferId = bufferView.buffer; - var buffer = gltf.buffers[bufferId]; - data.bufferId = bufferId; - data.buffer = buffer; - - var source = buffer.extras._pipeline.source; - var typedArray = arrayForComponentType(accessor.componentType, source, data.byteOffset, data.byteLength); - data.data = typedArray; - - data.min = []; - data.max = []; - - for (var i = 0; i < typedArray.length; i++) { - var element = typedArray[i]; - var index = i % numComponents; - if (index >= data.min.length) { - data.min.push(element); - data.max.push(element); - } - else { - if (element < data.min[index]) { - data.min[index] = element; - } - if (element > data.max[index]) { - data.max[index] = element; - } - } - } - return data; -} - -function getByteLengthForComponentType(type) { - switch (type) { - case 5120: - case 5121: - return 1; - case 5122: - case 5123: - return 2; - case 5126: - return 4; - } +function attributeIsQuantizable(attributeSemantic) { + return attributeSemantic.indexOf("NORMAL") >= 0 || + attributeSemantic.indexOf("POSITION") >= 0 || + attributeSemantic.indexOf("TEXCOORD") >= 0 || + attributeSemantic.indexOf("JOINT") >= 0 || + attributeSemantic.indexOf("WEIGHT") >= 0 || + attributeSemantic.indexOf("COLOR") >= 0; } -function arrayForComponentType(type, buffer, offset, length) { - var arrayBuffer; - switch (type) { - case 5120: - return new Int8Array(arrayBuffer); - case 5121: - return new Uint8Array(arrayBuffer); - case 5122: - return new Int16Array(arrayBuffer); - case 5123: - return new Uint16Array(arrayBuffer); - case 5126: - return toFloat32Array(buffer, offset, length); +function accessorIsQuantizable(accessor) { + // This accessor is already quantized + if (accessorIsQuantized(accessor)) { + return false; } + // Only 32-bit float to 16-bit int quantization is supported + return accessor.componentType == 5126; } -function toFloat32Array(buffer, offset, length) { - var nodeBuffer = new Buffer(buffer); - var array = new Float32Array(length / Float32Array.BYTES_PER_ELEMENT); - var i = 0; - var byteOffset = offset; - while(byteOffset < length + offset) { - array[i] = nodeBuffer.readFloatLE(byteOffset); - byteOffset += Float32Array.BYTES_PER_ELEMENT; - i++; +function accessorIsQuantized(accessor) { + var extensions = accessor.extensions; + if (defined(extensions)) { + return defined(extensions.WEB3D_quantized_attributes); } - return array; + return false; } \ No newline at end of file From 8596b8e8d268de4e127af73f18d16ec7e61b83d6 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Tue, 7 Jun 2016 12:13:00 -0400 Subject: [PATCH 11/36] Dropped unnecessary else --- lib/getAccessorByteStride.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/getAccessorByteStride.js b/lib/getAccessorByteStride.js index f4f55e79..41c007c7 100644 --- a/lib/getAccessorByteStride.js +++ b/lib/getAccessorByteStride.js @@ -15,7 +15,5 @@ function getAccessorByteStride(accessor) { if (accessor.byteStride > 0) { return accessor.byteStride; } - else { - return byteLengthForComponentType(accessor.componentType) * numberOfComponentsForType(accessor.type); - } + return byteLengthForComponentType(accessor.componentType) * numberOfComponentsForType(accessor.type); } \ No newline at end of file From 9a893aa7f29140d1ea0631c7e958d4301834319d Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Tue, 7 Jun 2016 14:23:19 -0400 Subject: [PATCH 12/36] Added packBuffers for minimizing buffer space --- lib/packBuffers.js | 131 ++++++++++++++++++++++++++++++++++++ lib/quantizeAttributes.js | 43 ++++++------ lib/removeUnusedVertices.js | 64 +----------------- 3 files changed, 155 insertions(+), 83 deletions(-) create mode 100644 lib/packBuffers.js diff --git a/lib/packBuffers.js b/lib/packBuffers.js new file mode 100644 index 00000000..2f2c02f8 --- /dev/null +++ b/lib/packBuffers.js @@ -0,0 +1,131 @@ +'use strict'; +var Cesium = require('cesium'); +var defined = Cesium.defined; + +var byteLengthForComponentType = require('./byteLengthForComponentType'); +var getAccessorByteStride = require('./getAccessorByteStride'); +var numberOfComponentsForType = require('./numberOfComponentsForType'); + +module.exports = packBuffers; + +/** + * Repacks the accessed buffer data into contiguous chunks. + * Also has the effect of un-interleaving interleaved accessors. + * + * @param gltf + */ +function packBuffers(gltf) { + var buffers = gltf.buffers; + var packBufferViews = {length : 0}; + for (var bufferId in buffers) { + if (buffers.hasOwnProperty(bufferId)) { + packBuffer(gltf, bufferId, packBufferViews); + } + } + delete packBufferViews.length + gltf.bufferViews = packBufferViews; +} + +function packBuffer(gltf, bufferId, packBufferViews) { + var buffers = gltf.buffers; + var buffer = buffers[bufferId]; + var source = buffer.extras._pipeline.source; + var packBuffer = new Uint8Array(source.length); + var accessors = accessorsByTargetAndByteLength(gltf, bufferId); + var offset = 0; + + var targets = [34962, 0, 34963]; + for (var i = 0; i < targets.length; i++) { + var target = targets[i]; + var accessorsByByteLength = accessors[target]; + if (defined(accessorsByByteLength)) { + offset = packAccessorsForTarget(gltf, bufferId, target, accessorsByByteLength, source, packBuffer, packBufferViews, offset); + } + } + buffer.extras._pipeline.source = new Uint8Array(packBuffer.buffer, 0, offset); + buffer.byteLength = offset; +} + +function packAccessorsForTarget(gltf, bufferId, target, accessorsByByteLength, sourceBuffer, packBuffer, packBufferViews, packOffset) { + var byteLengths = [4, 2, 1]; + for (var i = 0; i < byteLengths.length; i++) { + var byteLength = byteLengths[i]; + var accessorIds = accessorsByByteLength[byteLength]; + if (defined(accessorIds) && accessorIds.length > 0) { + // Byte-boundary align the offset if it isn't already + packOffset += packOffset % byteLength; + packOffset = packAccessors(gltf, bufferId, target, accessorIds, sourceBuffer, packBuffer, packBufferViews, packOffset); + } + } + return packOffset; +} + +function packAccessors(gltf, bufferId, target, accessorsToPack, sourceBuffer, packBuffer, packBufferViews, packOffset) { + var accessors = gltf.accessors; + var bufferViews = gltf.bufferViews; + var length = accessorsToPack.length; + var bufferViewId = 'bufferView_' + packBufferViews.length; + var bytesWritten = 0; + for (var i = 0; i < length; i++) { + var accessorId = accessorsToPack[i]; + var accessor = accessors[accessorId]; + var bufferView = bufferViews[accessor.bufferView]; + var byteStride = getAccessorByteStride(accessor); + var byteOffset = accessor.byteOffset + bufferView.byteOffset; + var numberOfComponents = numberOfComponentsForType(accessor.type); + var componentByteLength = byteLengthForComponentType(accessor.componentType); + var byteLength = numberOfComponents * componentByteLength; + var offset = byteOffset; + var count = accessor.count; + accessor.byteStride = 0; + accessor.bufferView = bufferViewId; + accessor.byteOffset = bytesWritten; + for (var num = 0; num < count; num++) { + for (var j = 0; j < byteLength; j++) { + packBuffer[packOffset + bytesWritten] = sourceBuffer[offset + j]; + bytesWritten++; + } + offset += byteStride; + } + } + var packBufferView = { + buffer : bufferId, + byteLength : bytesWritten, + byteOffset : packOffset + }; + if (target > 0) { + packBufferView.target = target; + } + packBufferViews[bufferViewId] = packBufferView; + packBufferViews.length++; + return packOffset + bytesWritten; +} + +function accessorsByTargetAndByteLength(gltf, bufferId) { + var accessors = gltf.accessors; + var bufferViews = gltf.bufferViews; + var accessorsByTargetAndByteLength = {}; + for (var accessorId in accessors) { + var accessor = accessors[accessorId]; + var bufferView = bufferViews[accessor.bufferView]; + if (bufferView.buffer === bufferId) { + var target = bufferView.target; + if (!defined(target)) { + target = 0; + } + var accessorsByByteLength = accessorsByTargetAndByteLength[target]; + if (!defined(accessorsByByteLength)) { + accessorsByByteLength = {}; + accessorsByTargetAndByteLength[target] = accessorsByByteLength; + } + var byteLength = byteLengthForComponentType(accessor.componentType); + var accessorIds = accessorsByByteLength[byteLength]; + if (!defined(accessorIds)) { + accessorIds = []; + accessorsByByteLength[byteLength] = accessorIds; + } + accessorIds.push(accessorId); + } + } + return accessorsByTargetAndByteLength; +} \ No newline at end of file diff --git a/lib/quantizeAttributes.js b/lib/quantizeAttributes.js index f6c9e97a..6ca72221 100644 --- a/lib/quantizeAttributes.js +++ b/lib/quantizeAttributes.js @@ -1,13 +1,12 @@ 'use strict'; var Cesium = require('cesium'); - var defined = Cesium.defined; var addExtensionsUsed = require('./addExtensionsUsed'); var byteLengthForComponentType = require('./byteLengthForComponentType'); var getAccessorByteStride = require('./getAccessorByteStride'); var numberOfComponentsForType = require('./numberOfComponentsForType'); -var removeUnusedVertices = require('./removeUnusedVertices'); +var packBuffers = require('./packBuffers'); module.exports = quantizeAttributes; @@ -69,9 +68,6 @@ function quantizeAttributes(gltf, options) { var i, j; var accessorIds = {}; var precision = undefined; - if (defined(options.precision)) { - precision = 10^options.precision; - } var range = Math.pow(2, 16) - 1; // Retrieve the accessors that should be quantized @@ -82,13 +78,16 @@ function quantizeAttributes(gltf, options) { quantizeAll = false; accessorIds = getQuantizableAttributes(gltf, attributes); } + if (defined(options.precision)) { + precision = 10^options.precision; + } } // Quantize all valid attributes if (quantizeAll) { accessorIds = getAllQuantizableAttributes(gltf); } - // Generate quantized attributes and table for repacking the buffer + // Generate quantized attributes var isQuantized = false; for (var accessorId in accessorIds) { if (accessorIds.hasOwnProperty(accessorId)) { @@ -142,7 +141,7 @@ function quantizeAttributes(gltf, options) { } if (isQuantized) { // Repack the buffers - removeUnusedVertices(gltf); + packBuffers(gltf); // Finalize addExtensionsUsed(gltf, 'WEB3D_quantized_attributes'); } @@ -160,26 +159,26 @@ function createDecodeMatrix(min, max, range) { } function createDecodeMatrix3(min, max, range) { - return [(max[0] - min[0])/range, 0, 0, - 0, (max[1] - min[1])/range, 0, - min[0], min[1], 1 + return [(max[0] - min[0])/range, 0.0, 0.0, + 0.0, (max[1] - min[1])/range, 0.0, + min[0], min[1], 1.0 ]; } function createDecodeMatrix4(min, max, range) { - return [(max[0] - min[0])/range, 0, 0, 0, - 0, (max[1] - min[1])/range, 0, 0, - 0, 0, (max[2] - min[2])/range, 0, - min[0], min[1], min[2], 1 + return [(max[0] - min[0])/range, 0.0, 0.0, 0.0, + 0.0, (max[1] - min[1])/range, 0.0, 0.0, + 0.0, 0.0, (max[2] - min[2])/range, 0.0, + min[0], min[1], min[2], 1.0 ]; } function createDecodeMatrix5(min, max, range) { - return [(max[0] - min[0])/range, 0, 0, 0, 0, - 0, (max[1] - min[1])/range, 0, 0, 0, - 0, 0, (max[2] - min[2])/range, 0, 0, - 0, 0, 0, (max[3] - min[3])/range, 0, - min[0], min[1], min[2], min[3], 1 + return [(max[0] - min[0])/range, 0.0, 0.0, 0.0, 0.0, + 0.0, (max[1] - min[1])/range, 0.0, 0.0, 0.0, + 0.0, 0.0, (max[2] - min[2])/range, 0.0, 0.0, + 0.0, 0.0, 0.0, (max[3] - min[3])/range, 0.0, + min[0], min[1], min[2], min[3], 1.0 ]; } @@ -228,7 +227,7 @@ function getAllQuantizableAttributes(gltf) { var mesh = meshes[meshId]; var primitives = mesh.primitives; if (defined(primitives)) { - for (i = 0; i < primitives.length; i++) { + for (var i = 0; i < primitives.length; i++) { var primitive = primitives[i]; var attributes = primitive.attributes; if (defined(attributes)) { @@ -236,10 +235,10 @@ function getAllQuantizableAttributes(gltf) { if (attributes.hasOwnProperty(attribute)) { if (attributeIsQuantizable(attribute)) { var accessorId = attributes[attribute]; - if (defined(visitedAccessors[accessorId])) { + if (!defined(visitedAccessors[accessorId])) { var accessor = accessors[accessorId]; if (accessorIsQuantizable(accessor)) { - accessorAttributes[accessor] = attribute; + accessorAttributes[accessorId] = attribute; } visitedAccessors[accessorId] = true; } diff --git a/lib/removeUnusedVertices.js b/lib/removeUnusedVertices.js index 8d3f6a63..99069fb0 100755 --- a/lib/removeUnusedVertices.js +++ b/lib/removeUnusedVertices.js @@ -6,78 +6,20 @@ var defaultValue = Cesium.defaultValue; var WebGLConstants = Cesium.WebGLConstants; var objectValues = require('object-values'); +var packBuffers = require('./packBuffers'); + module.exports = removeUnusedVertices; //Removes sections of buffers which are unreferenced by buffer views and accessors. function removeUnusedVertices(gltf) { if (defined(gltf.accessors) && defined(gltf.buffers) && defined(gltf.bufferViews) && defined(gltf.meshes)) { - removeUnusedBufferData(gltf); + packBuffers(gltf); removeUnusedIndexData(gltf); } return gltf; } -//Removes sections of buffers which are not referenced by buffer views and updates its corresponding buffer views. -function removeUnusedBufferData(gltf) { - var buffers = gltf.buffers; - var bufferViews = gltf.bufferViews; - - //Create and traverse an array of buffer views sorted by increasing byteOffset - var sortedBufferViews = objectValues(bufferViews); - sortedBufferViews.sort(function(a, b) { - return a.byteOffset - b.byteOffset; - }); - var bufferViewsLength = sortedBufferViews.length; - for (var i = 0; i < bufferViewsLength; i++) { - var bufferView = sortedBufferViews[i]; - var viewedBuffer = buffers[bufferView.buffer]; - var source = viewedBuffer.extras._pipeline.source; - var viewStart = bufferView.byteOffset; - var viewLength = defaultValue(bufferView.byteLength, 0); - var viewEnd = viewStart + viewLength; - - //While processing the buffer views, store the accumulated length of deleted portions of referenced buffers in the offset extra - //The end extra property denotes the largest index in the buffer referenced thus far by buffer views. - if (!defined(viewedBuffer.extras._pipeline.offset)) { - viewedBuffer.extras._pipeline.offset = viewStart; - viewedBuffer.extras._pipeline.end = viewStart + viewLength; - if (viewStart > 0) { - bufferView.byteOffset = 0; - viewedBuffer.extras._pipeline.source = source.slice(viewStart); - } - } - else { - if (viewStart > viewedBuffer.extras._pipeline.end) { - viewedBuffer.extras._pipeline.source = Buffer.concat([source.slice(0, viewedBuffer.extras._pipeline.end - viewedBuffer.extras._pipeline.offset), - source.slice(viewStart - viewedBuffer.extras._pipeline.offset)]); - viewedBuffer.extras._pipeline.offset += viewStart - viewedBuffer.extras._pipeline.end; - } - bufferView.byteOffset -= viewedBuffer.extras._pipeline.offset; - - if (viewEnd > viewedBuffer.extras._pipeline.end) { - viewedBuffer.extras._pipeline.end = viewEnd; - } - } - } - - //Update the buffers based on their new source, offset, and end. The buffer will be deleted if it was never referenced. - for (var bufferId in buffers) { - if (buffers.hasOwnProperty(bufferId)) { - var buffer = buffers[bufferId]; - if (!defined(buffer.extras._pipeline.offset)) { - delete buffers[bufferId]; - } - else { - buffer.extras._pipeline.source = buffer.extras._pipeline.source.slice(0, buffer.extras._pipeline.end - buffer.extras._pipeline.offset); - buffer.byteLength = buffer.extras._pipeline.source.length; - delete buffer.extras._pipeline.offset; - delete buffer.extras._pipeline.end; - } - } - } -} - //Remove sections of buffers which are not accessed by indices. function removeUnusedIndexData(gltf) { var accessors = gltf.accessors; From 4b2c2b52e62e98ab70c1285b34b9a891582bb39b Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Tue, 7 Jun 2016 15:01:32 -0400 Subject: [PATCH 13/36] Reverted removeUnusedVertices --- lib/removeUnusedVertices.js | 64 +++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/lib/removeUnusedVertices.js b/lib/removeUnusedVertices.js index 99069fb0..8d3f6a63 100755 --- a/lib/removeUnusedVertices.js +++ b/lib/removeUnusedVertices.js @@ -6,20 +6,78 @@ var defaultValue = Cesium.defaultValue; var WebGLConstants = Cesium.WebGLConstants; var objectValues = require('object-values'); -var packBuffers = require('./packBuffers'); - module.exports = removeUnusedVertices; //Removes sections of buffers which are unreferenced by buffer views and accessors. function removeUnusedVertices(gltf) { if (defined(gltf.accessors) && defined(gltf.buffers) && defined(gltf.bufferViews) && defined(gltf.meshes)) { - packBuffers(gltf); + removeUnusedBufferData(gltf); removeUnusedIndexData(gltf); } return gltf; } +//Removes sections of buffers which are not referenced by buffer views and updates its corresponding buffer views. +function removeUnusedBufferData(gltf) { + var buffers = gltf.buffers; + var bufferViews = gltf.bufferViews; + + //Create and traverse an array of buffer views sorted by increasing byteOffset + var sortedBufferViews = objectValues(bufferViews); + sortedBufferViews.sort(function(a, b) { + return a.byteOffset - b.byteOffset; + }); + var bufferViewsLength = sortedBufferViews.length; + for (var i = 0; i < bufferViewsLength; i++) { + var bufferView = sortedBufferViews[i]; + var viewedBuffer = buffers[bufferView.buffer]; + var source = viewedBuffer.extras._pipeline.source; + var viewStart = bufferView.byteOffset; + var viewLength = defaultValue(bufferView.byteLength, 0); + var viewEnd = viewStart + viewLength; + + //While processing the buffer views, store the accumulated length of deleted portions of referenced buffers in the offset extra + //The end extra property denotes the largest index in the buffer referenced thus far by buffer views. + if (!defined(viewedBuffer.extras._pipeline.offset)) { + viewedBuffer.extras._pipeline.offset = viewStart; + viewedBuffer.extras._pipeline.end = viewStart + viewLength; + if (viewStart > 0) { + bufferView.byteOffset = 0; + viewedBuffer.extras._pipeline.source = source.slice(viewStart); + } + } + else { + if (viewStart > viewedBuffer.extras._pipeline.end) { + viewedBuffer.extras._pipeline.source = Buffer.concat([source.slice(0, viewedBuffer.extras._pipeline.end - viewedBuffer.extras._pipeline.offset), + source.slice(viewStart - viewedBuffer.extras._pipeline.offset)]); + viewedBuffer.extras._pipeline.offset += viewStart - viewedBuffer.extras._pipeline.end; + } + bufferView.byteOffset -= viewedBuffer.extras._pipeline.offset; + + if (viewEnd > viewedBuffer.extras._pipeline.end) { + viewedBuffer.extras._pipeline.end = viewEnd; + } + } + } + + //Update the buffers based on their new source, offset, and end. The buffer will be deleted if it was never referenced. + for (var bufferId in buffers) { + if (buffers.hasOwnProperty(bufferId)) { + var buffer = buffers[bufferId]; + if (!defined(buffer.extras._pipeline.offset)) { + delete buffers[bufferId]; + } + else { + buffer.extras._pipeline.source = buffer.extras._pipeline.source.slice(0, buffer.extras._pipeline.end - buffer.extras._pipeline.offset); + buffer.byteLength = buffer.extras._pipeline.source.length; + delete buffer.extras._pipeline.offset; + delete buffer.extras._pipeline.end; + } + } + } +} + //Remove sections of buffers which are not accessed by indices. function removeUnusedIndexData(gltf) { var accessors = gltf.accessors; From c5c48455c349360ad9750c66b7f73ec978879039 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Tue, 7 Jun 2016 15:03:20 -0400 Subject: [PATCH 14/36] Made decode matrix 0's floats and simplified options --- lib/quantizeAttributes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/quantizeAttributes.js b/lib/quantizeAttributes.js index 6ca72221..3d0decc7 100644 --- a/lib/quantizeAttributes.js +++ b/lib/quantizeAttributes.js @@ -65,7 +65,7 @@ function quantizeAttributes(gltf, options) { var accessors = gltf.accessors; var bufferViews = gltf.bufferViews; var buffers = gltf.buffers; - var i, j; + var i; var accessorIds = {}; var precision = undefined; var range = Math.pow(2, 16) - 1; From 9183718824ab7b9f8a6d097e2f46ff8d406ab9d8 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Tue, 7 Jun 2016 15:19:42 -0400 Subject: [PATCH 15/36] More updates --- lib/getBinaryGltf.js | 28 +++++++------ lib/quantizeAttributes.js | 83 +++++++++++---------------------------- lib/writeBinaryGltf.js | 4 +- 3 files changed, 40 insertions(+), 75 deletions(-) diff --git a/lib/getBinaryGltf.js b/lib/getBinaryGltf.js index 3e3f0fba..a5f1da51 100644 --- a/lib/getBinaryGltf.js +++ b/lib/getBinaryGltf.js @@ -10,7 +10,8 @@ var removePipelineExtras = require('./removePipelineExtras'); module.exports = getBinaryGltf; //Update object with KHR_binary_glTF properties and add to body and bufferViews -function updateBinaryObject(gltf, pipelineExtras, name, offset) { +function updateBinaryObject(gltf, pipelineExtras, name, state) { + var bufferViews = gltf.bufferViews; var objects = gltf[name]; if (defined(objects)) { for (var objectId in objects) { @@ -25,19 +26,19 @@ function updateBinaryObject(gltf, pipelineExtras, name, offset) { //Create a bufferView based on the byte length and current offset var bufferViewKeys = Object.keys(bufferViews); - while (bufferViewKeys.indexOf('binary_bufferView' + currentBinaryView) != -1) { - currentBinaryView++; + while (bufferViewKeys.indexOf('binary_bufferView' + state.currentBinaryView) != -1) { + state.currentBinaryView++; } var objectSource = object.extras._pipeline.source; - var bufferViewId = 'binary_bufferView' + currentBinaryView; + var bufferViewId = 'binary_bufferView' + state.currentBinaryView; KHR_binary_glTF.bufferView = bufferViewId; //Create bufferview bufferViews[bufferViewId] = { buffer : 'binary_glTF', byteLength : objectSource.length, - byteOffset : currentOffset + byteOffset : state.offset }; - offset += objectSource.length; + state.offset += objectSource.length; //Append the object source to the binary body pipelineExtras.source = Buffer.concat([pipelineExtras.source, objectSource]); @@ -53,7 +54,6 @@ function updateBinaryObject(gltf, pipelineExtras, name, offset) { } } } - return offset; } function getBinaryGltf(gltf, callback) { @@ -62,17 +62,19 @@ function getBinaryGltf(gltf, callback) { gltf.buffers = defaultValue(gltf.buffers, {}); mergeBuffers(gltf, 'binary_glTF'); - var bufferViews = gltf.bufferViews; var buffers = gltf.buffers; var currentOffset = buffers.binary_glTF.byteLength; - var pipelineExtras = buffers.binarya_glTF.extras._pipeline; - var currentBinaryView = 0; + var pipelineExtras = buffers.binary_glTF.extras._pipeline; + var state = { + offset : currentOffset, + currentBinaryView : 0 + }; - currentOffset = updateBinaryObject(gltf, pipelineExtras, 'shaders', currentOffset); - currentOffset = updateBinaryObject(gltf, pipelineExtras, 'images', currentOffset); + updateBinaryObject(gltf, pipelineExtras, 'shaders', state); + updateBinaryObject(gltf, pipelineExtras, 'images', state); var body = buffers.binary_glTF.extras._pipeline.source; - buffers.binary_glTF.byteLength = currentOffset; + buffers.binary_glTF.byteLength = state.offset; //Remove extras objects before writing removePipelineExtras(gltf); diff --git a/lib/quantizeAttributes.js b/lib/quantizeAttributes.js index 3d0decc7..f2b308be 100644 --- a/lib/quantizeAttributes.js +++ b/lib/quantizeAttributes.js @@ -18,7 +18,7 @@ module.exports = quantizeAttributes; * * @param {Object} [gltf] A javascript object holding a glTF hierarchy. * @param {Object} [options=undefined] Defines more specific quantization behavior. - * @param {Object} [options.attributes=undefined] Defines specific attributes to be quantized. This should be as arranged as meshId -> primitiveIndex -> [semantic_0, semantic_1, ... semantic_N] + * @param {Object} [options.semantics=undefined] Defines which semantics should be quantized. * @param {Number} [options.precision=undefined] Restricts the number of decimal places in the decodeMatrix. * * @returns glTF with quantized attributes @@ -52,13 +52,9 @@ module.exports = quantizeAttributes; * precision: 6 * } * - * // Quantize just the positions of a specific mesh + * // Quantize just the positions and normals of a model * quantizeAttributes(gltf, { - * attributes: { - * geometry: { - * 0: ['POSITION'] - * } - * } + * semantics: [POSITION, NORMAL] * }); */ function quantizeAttributes(gltf, options) { @@ -66,26 +62,26 @@ function quantizeAttributes(gltf, options) { var bufferViews = gltf.bufferViews; var buffers = gltf.buffers; var i; - var accessorIds = {}; + var accessorIds; var precision = undefined; var range = Math.pow(2, 16) - 1; // Retrieve the accessors that should be quantized - var quantizeAll = true; + var validSemantics = undefined; if (defined(options)) { - attributes = options.attributes; - if (defined(attributes)) { - quantizeAll = false; - accessorIds = getQuantizableAttributes(gltf, attributes); - } if (defined(options.precision)) { precision = 10^options.precision; } + var semantics = options.semantics; + if (defined(semantics)) { + for (i = 0; i < semantics.length; i++) { + var semantic = semantics[i]; + validSemantics[semantic] = true; + } + } } // Quantize all valid attributes - if (quantizeAll) { - accessorIds = getAllQuantizableAttributes(gltf); - } + accessorIds = getAllQuantizableAttributes(gltf, validSemantics); // Generate quantized attributes var isQuantized = false; @@ -182,42 +178,7 @@ function createDecodeMatrix5(min, max, range) { ]; } -function getQuantizableAttributes(gltf, attributes) { - var meshes = gltf.meshes; - var accessorIds = {}; - for (var meshId in meshes) { - if (meshes.hasOwnProperty(meshId)) { - var meshAttributes = attributes[meshId]; - if (defined(meshAttributes)) { - var primitives = meshes[meshId].primitives; - if (defined(primitives)) { - for (var stringIndex in meshAttributes) { - if (meshAttributes.hasOwnProperty(stringIndex)) { - var attributeArray = meshAttributes[stringIndex]; - var index = parseInt(stringIndex); - var primitive = primitives[index]; - if (defined(primitive)) { - for (var i = 0; i < attributeArray.length; i++) { - var attribute = attributeArray[i]; - var primitiveAttributes = primitive.attributes; - if (defined(primitiveAttributes)) { - var accessor = primitiveAttributes[attribute]; - if (defined(accessor)) { - accessorIds[accessor] = attribute; - } - } - } - } - } - } - } - } - } - } - return accessorIds; -} - -function getAllQuantizableAttributes(gltf) { +function getAllQuantizableAttributes(gltf, validSemantics) { var accessors = gltf.accessors; var meshes = gltf.meshes; var visitedAccessors = {}; @@ -233,14 +194,16 @@ function getAllQuantizableAttributes(gltf) { if (defined(attributes)) { for (var attribute in attributes) { if (attributes.hasOwnProperty(attribute)) { - if (attributeIsQuantizable(attribute)) { - var accessorId = attributes[attribute]; - if (!defined(visitedAccessors[accessorId])) { - var accessor = accessors[accessorId]; - if (accessorIsQuantizable(accessor)) { - accessorAttributes[accessorId] = attribute; + if (!defined(validSemantics) || defined(validSemantics[attribute])) { + if (attributeIsQuantizable(attribute)) { + var accessorId = attributes[attribute]; + if (!defined(visitedAccessors[accessorId])) { + var accessor = accessors[accessorId]; + if (accessorIsQuantizable(accessor)) { + accessorAttributes[accessorId] = attribute; + } + visitedAccessors[accessorId] = true; } - visitedAccessors[accessorId] = true; } } } diff --git a/lib/writeBinaryGltf.js b/lib/writeBinaryGltf.js index 30aa3aac..18c29921 100644 --- a/lib/writeBinaryGltf.js +++ b/lib/writeBinaryGltf.js @@ -2,7 +2,7 @@ var fs = require('fs'); var path = require('path'); var mkdirp = require('mkdirp'); -var getBinaryGltf = requilre('./getBinaryGltf'); +var getBinaryGltf = require('./getBinaryGltf'); module.exports = writeBinaryGltf; @@ -17,7 +17,7 @@ function writeBinaryGltf(gltf, outputPath, createDirectory, callback) { fs.writeFile(outputPath, glb, function (err) { if (err) { - throw err; + callback(err); } }); } \ No newline at end of file From 92280c8d4ead005127948277bba93e7d7686cc8c Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Tue, 7 Jun 2016 15:23:52 -0400 Subject: [PATCH 16/36] Fixed jsHint errors --- lib/byteLengthForComponentType.js | 4 ++-- lib/numberOfComponentsForType.js | 4 ++-- lib/packBuffers.js | 10 +++++----- lib/quantizeAttributes.js | 13 +++++++------ 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/byteLengthForComponentType.js b/lib/byteLengthForComponentType.js index dd4c6106..80bffc7e 100644 --- a/lib/byteLengthForComponentType.js +++ b/lib/byteLengthForComponentType.js @@ -1,4 +1,4 @@ -'use strict' +'use strict'; module.exports = byteLengthForComponentType; @@ -23,6 +23,6 @@ function byteLengthForComponentType(componentType) { case 5123: return 2; case 5126: - return 4 + return 4; } } \ No newline at end of file diff --git a/lib/numberOfComponentsForType.js b/lib/numberOfComponentsForType.js index d892bb85..b9cbc709 100644 --- a/lib/numberOfComponentsForType.js +++ b/lib/numberOfComponentsForType.js @@ -1,6 +1,6 @@ 'use strict'; -module.exports = numberOfComponentsForType +module.exports = numberOfComponentsForType; /** * Utility function for retrieving the number of components in a given type. @@ -30,6 +30,6 @@ function numberOfComponentsForType(type) { case 'MAT3': return 9; case 'MAT4': - return 16 + return 16; } } \ No newline at end of file diff --git a/lib/packBuffers.js b/lib/packBuffers.js index 2f2c02f8..78376ba8 100644 --- a/lib/packBuffers.js +++ b/lib/packBuffers.js @@ -19,19 +19,19 @@ function packBuffers(gltf) { var packBufferViews = {length : 0}; for (var bufferId in buffers) { if (buffers.hasOwnProperty(bufferId)) { - packBuffer(gltf, bufferId, packBufferViews); + packGltfBuffer(gltf, bufferId, packBufferViews); } } - delete packBufferViews.length + delete packBufferViews.length; gltf.bufferViews = packBufferViews; } -function packBuffer(gltf, bufferId, packBufferViews) { +function packGltfBuffer(gltf, bufferId, packBufferViews) { var buffers = gltf.buffers; var buffer = buffers[bufferId]; var source = buffer.extras._pipeline.source; var packBuffer = new Uint8Array(source.length); - var accessors = accessorsByTargetAndByteLength(gltf, bufferId); + var accessors = getAccessorsByTargetAndByteLength(gltf, bufferId); var offset = 0; var targets = [34962, 0, 34963]; @@ -101,7 +101,7 @@ function packAccessors(gltf, bufferId, target, accessorsToPack, sourceBuffer, pa return packOffset + bytesWritten; } -function accessorsByTargetAndByteLength(gltf, bufferId) { +function getAccessorsByTargetAndByteLength(gltf, bufferId) { var accessors = gltf.accessors; var bufferViews = gltf.bufferViews; var accessorsByTargetAndByteLength = {}; diff --git a/lib/quantizeAttributes.js b/lib/quantizeAttributes.js index f2b308be..0ae072e0 100644 --- a/lib/quantizeAttributes.js +++ b/lib/quantizeAttributes.js @@ -61,13 +61,14 @@ function quantizeAttributes(gltf, options) { var accessors = gltf.accessors; var bufferViews = gltf.bufferViews; var buffers = gltf.buffers; + var value; var i; var accessorIds; - var precision = undefined; + var precision; var range = Math.pow(2, 16) - 1; // Retrieve the accessors that should be quantized - var validSemantics = undefined; + var validSemantics; if (defined(options)) { if (defined(options.precision)) { precision = 10^options.precision; @@ -99,14 +100,14 @@ function quantizeAttributes(gltf, options) { // accessor min and max are the extremes of the range var min = accessor.min; var max = accessor.max; - accessor.min = Array(min.length).fill(0); - accessor.max = Array(max.length).fill(range); + accessor.min = new Array(min.length).fill(0); + accessor.max = new Array(max.length).fill(range); var decodeMatrix = createDecodeMatrix(min, max, range); // change matrix precision to save space if (defined(precision)) { for (i = 0; i < decodeMatrix.length; i++) { - var value = decodeMatrix[i]; + value = decodeMatrix[i]; decodeMatrix[i] = Math.floor(value * precision) / precision; } } @@ -125,7 +126,7 @@ function quantizeAttributes(gltf, options) { var numberOfComponents = numberOfComponentsForType(accessor.type); for (i = offset; num < count; i+=byteStride) { for (var j = 0; j < numberOfComponents; j++) { - var value = source.readFloatLE(i + j * componentByteLength); + value = source.readFloatLE(i + j * componentByteLength); var encoded = Math.round((value - min[j]) * range / (max[j] - min[j])); source.writeUInt16LE(encoded, i + j * 2); } From cc98b05b9119e16cf8d962ed91f9dfc37f288ad5 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Tue, 7 Jun 2016 16:19:19 -0400 Subject: [PATCH 17/36] Added tests for packBuffers --- specs/lib/packBuffersSpec.js | 118 +++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 specs/lib/packBuffersSpec.js diff --git a/specs/lib/packBuffersSpec.js b/specs/lib/packBuffersSpec.js new file mode 100644 index 00000000..daa3a14b --- /dev/null +++ b/specs/lib/packBuffersSpec.js @@ -0,0 +1,118 @@ +'use strict'; +var clone = require('clone'); + +var byteLengthForComponentType = require('../../lib/byteLengthForComponentType'); +var numberOfComponentsForType = require('../../lib/numberOfComponentsForType'); +var packBuffers = require('../../lib/packBuffers'); + +describe('packBuffers', function() { + var buffer = new Uint8Array(96); + var testGltf = { + accessors : { + // Interleaved accessors in bufferView_0 + accessor_0 : { + bufferView : 'bufferView_0', + byteOffset : 0, + byteStride : 18, + componentType : 5126, + count : 3, + type : 'VEC3' + }, + accessor_1 : { + bufferView : 'bufferView_0', + byteOffset : 12, + byteStride : 18, + componentType : 5123, + count : 3, + type : 'VEC2' + }, + // Block accessors in bufferView_1 + accessor_2 : { + bufferView : 'bufferView_1', + byteOffset : 0, + byteStride : 12, + componentType : 5126, + count : 3, + type : 'VEC3' + }, + accessor_3 : { + bufferView : 'bufferView_1', + byteOffset : 36, + componentType : 5123, + count : 3, + type : 'VEC2' + } + }, + bufferViews : { + bufferView_0 : { + buffer : 'buffer', + byteLength : 48, + byteOffset : 0, + target : 34962 + }, + bufferView_1 : { + buffer : 'buffer', + byteLength : 48, + byteOffset : 48, + target : 34963 + } + }, + buffers : { + buffer : { + byteLength : buffer.length, + type : 'arraybuffer', + extras : { + _pipeline : {} + } + } + } + }; + + it('doesn\'t remove any data if the whole buffer is used', function() { + var gltf = clone(testGltf); + gltf.buffers.buffer.extras._pipeline.source = buffer; + packBuffers(gltf); + expect(gltf.buffers.buffer.byteLength).toEqual(testGltf.buffers.buffer.byteLength); + }); + + it('removes extra trailing data on the buffer', function() { + var gltf = clone(testGltf); + gltf.buffers.buffer.extras._pipeline.source = new Uint8Array(buffer.length * 2); + packBuffers(gltf); + expect(gltf.buffers.buffer.byteLength).toEqual(testGltf.buffers.buffer.byteLength); + }); + + it('removes interleaved unused data', function() { + var gltf = clone(testGltf); + gltf.buffers.buffer.extras._pipeline.source = buffer; + var deletedAccessorId = 'accessor_1'; + var deletedAccessor = gltf.accessors[deletedAccessorId]; + var size = byteLengthForComponentType(deletedAccessor.componentType) * numberOfComponentsForType(deletedAccessor.type) * deletedAccessor.count; + delete gltf.accessors[deletedAccessorId]; + packBuffers(gltf); + expect(gltf.buffers.buffer.byteLength + size).toEqual(testGltf.buffers.buffer.byteLength); + }); + + it('removes block unused data', function() { + var gltf = clone(testGltf); + gltf.buffers.buffer.extras._pipeline.source = buffer; + var deletedAccessorId = 'accessor_2'; + var deletedAccessor = gltf.accessors[deletedAccessorId]; + var size = byteLengthForComponentType(deletedAccessor.componentType) * numberOfComponentsForType(deletedAccessor.type) * deletedAccessor.count; + delete gltf.accessors[deletedAccessorId]; + packBuffers(gltf); + expect(gltf.buffers.buffer.byteLength + size).toEqual(testGltf.buffers.buffer.byteLength); + }); + + it('removes unused bufferView', function() { + var gltf = clone(testGltf); + gltf.buffers.buffer.extras._pipeline.source = buffer; + var size = gltf.bufferViews.bufferView_0.byteLength; + delete gltf.accessors.accessor_0; + delete gltf.accessors.accessor_1; + packBuffers(gltf); + expect(gltf.buffers.buffer.byteLength + size).toEqual(testGltf.buffers.buffer.byteLength); + var bufferViewCount = Object.keys(gltf.bufferViews).length; + expect(bufferViewCount).toEqual(1); + }); +}); \ No newline at end of file From 9ad9112e45e7f25c2dc48d79f5c23a61a117f481 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Tue, 7 Jun 2016 16:19:35 -0400 Subject: [PATCH 18/36] Updated packBuffers --- lib/packBuffers.js | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/packBuffers.js b/lib/packBuffers.js index 78376ba8..ca9011c0 100644 --- a/lib/packBuffers.js +++ b/lib/packBuffers.js @@ -47,6 +47,8 @@ function packGltfBuffer(gltf, bufferId, packBufferViews) { } function packAccessorsForTarget(gltf, bufferId, target, accessorsByByteLength, sourceBuffer, packBuffer, packBufferViews, packOffset) { + var bufferViewId = 'bufferView_' + packBufferViews.length; + var originalOffset = packOffset; var byteLengths = [4, 2, 1]; for (var i = 0; i < byteLengths.length; i++) { var byteLength = byteLengths[i]; @@ -54,17 +56,26 @@ function packAccessorsForTarget(gltf, bufferId, target, accessorsByByteLength, s if (defined(accessorIds) && accessorIds.length > 0) { // Byte-boundary align the offset if it isn't already packOffset += packOffset % byteLength; - packOffset = packAccessors(gltf, bufferId, target, accessorIds, sourceBuffer, packBuffer, packBufferViews, packOffset); + packOffset = packAccessors(gltf, bufferId, target, accessorIds, sourceBuffer, packBuffer, bufferViewId, packOffset); } } + var packBufferView = { + buffer : bufferId, + byteLength : packOffset - originalOffset, + byteOffset : originalOffset + }; + if (target > 0) { + packBufferView.target = target; + } + packBufferViews[bufferViewId] = packBufferView; + packBufferViews.length++; return packOffset; } -function packAccessors(gltf, bufferId, target, accessorsToPack, sourceBuffer, packBuffer, packBufferViews, packOffset) { +function packAccessors(gltf, bufferId, target, accessorsToPack, sourceBuffer, packBuffer, bufferViewId, packOffset) { var accessors = gltf.accessors; var bufferViews = gltf.bufferViews; var length = accessorsToPack.length; - var bufferViewId = 'bufferView_' + packBufferViews.length; var bytesWritten = 0; for (var i = 0; i < length; i++) { var accessorId = accessorsToPack[i]; @@ -88,16 +99,6 @@ function packAccessors(gltf, bufferId, target, accessorsToPack, sourceBuffer, pa offset += byteStride; } } - var packBufferView = { - buffer : bufferId, - byteLength : bytesWritten, - byteOffset : packOffset - }; - if (target > 0) { - packBufferView.target = target; - } - packBufferViews[bufferViewId] = packBufferView; - packBufferViews.length++; return packOffset + bytesWritten; } From e045dd32194f3829deef57861dea3013dfbdf5b5 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Tue, 7 Jun 2016 16:50:30 -0400 Subject: [PATCH 19/36] Added quantized attribute tests --- specs/lib/quantizeAttributesSpec.js | 159 +++++++++++++++++++++++----- 1 file changed, 131 insertions(+), 28 deletions(-) diff --git a/specs/lib/quantizeAttributesSpec.js b/specs/lib/quantizeAttributesSpec.js index 4e9c92f3..6c1a82d6 100644 --- a/specs/lib/quantizeAttributesSpec.js +++ b/specs/lib/quantizeAttributesSpec.js @@ -1,38 +1,141 @@ 'use strict'; +var clone = require('clone'); -var fs = require('fs'); +var byteLengthForComponentType = require('../../lib/byteLengthForComponentType'); +var numberOfComponentsForType = require('../../lib/numberOfComponentsForType'); var quantizeAttributes = require('../../lib/quantizeAttributes'); -var addPipelineExtras = require('../../lib/addPipelineExtras'); -var loadGltfUris = require('../../lib/loadGltfUris'); -var writeGltf = require('../../lib/writeGltf') -var boxPath = './specs/data/quantized/'; -var outputPath = './output/Duck-Quantized.gltf'; describe('quantizeAttributes', function() { - var gltf; - - beforeAll(function(done) { - fs.readFile(boxPath + 'Duck.gltf', function(err, data) { - if(!err) { - gltf = JSON.parse(data); - addPipelineExtras(gltf); - loadGltfUris(gltf, boxPath, function() { - done(); - }); + var buffer = new Buffer(new Uint8Array(108)); + var testGltf = { + accessors : { + // Interleaved accessors in bufferView_0 + accessor_0 : { + bufferView : 'bufferView_0', + byteOffset : 0, + byteStride : 18, + componentType : 5126, + count : 3, + min : [-1.0, -1.0, -1.0], + max : [1.0, 1.0, 1.0], + type : 'VEC3', + }, + accessor_1 : { + bufferView : 'bufferView_0', + byteOffset : 12, + byteStride : 18, + componentType : 5123, + count : 3, + min : [-1.0, -1.0, -1.0], + max : [1.0, 1.0, 1.0], + type : 'VEC2' + }, + // Block accessors in bufferView_1 + accessor_2 : { + bufferView : 'bufferView_1', + byteOffset : 0, + byteStride : 12, + componentType : 5126, + count : 3, + min : [-1.0, -1.0, -1.0], + max : [1.0, 1.0, 1.0], + type : 'VEC3' + }, + // Already quantized + accessor_3 : { + bufferView : 'bufferView_1', + byteOffset : 36, + componentType : 5126, + count : 3, + min : [0, 0, 0], + max : [65535, 65535, 65535], + type : 'VEC2', + extensions : { + WEB3D_quantized_attributes : { + decodeMatrix : [ + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0 + ], + decodeMin : [-1.0, -1.0], + decodeMax : [1.0, 1.0] + } + } } - }); - }); - - it('quantizes positions', function() { - quantizeAttributes(gltf, { - "attributes": { - "LOD3spShape-lib": { - "0": [ - "POSITION" - ] + }, + bufferViews : { + bufferView_0 : { + buffer : 'buffer', + byteLength : 48, + byteOffset : 0, + target : 34962 + }, + bufferView_1 : { + buffer : 'buffer', + byteLength : 60, + byteOffset : 48, + target : 34962 + } + }, + buffers : { + buffer : { + byteLength : buffer.length, + type : 'arraybuffer', + extras : { + _pipeline : {} } } - }); - writeGltf(gltf, outputPath, true); + }, + meshes : { + mesh : { + primitives : [ + { + attributes : { + POSITION : 'accessor_0', + NORMAL : 'accessor_1' + } + }, + { + attributes : { + POSITION : 'accessor_2', + TEXCOORD : 'accessor_3' + } + } + ] + } + } + }; + + it('Doesn\'t quantize if options.semantics is empty', function() { + var gltf = clone(testGltf); + gltf.buffers.buffer.extras._pipeline.source = buffer; + quantizeAttributes(gltf, {semantics: []}); + expect(gltf.buffers.buffer.byteLength).toEqual(buffer.length); + }); + + it('Quantizes attributes for semantic', function() { + var gltf = clone(testGltf); + var accessor_0 = gltf.accessors.accessor_0; + var accessor_2 = gltf.accessors.accessor_2; + var size = byteLengthForComponentType(accessor_0.componentType) * numberOfComponentsForType(accessor_0.type) * accessor_0.count; + size += byteLengthForComponentType(accessor_2.componentType) * numberOfComponentsForType(accessor_2.type) * accessor_2.count; + size = size/2.0; + gltf.buffers.buffer.extras._pipeline.source = buffer; + quantizeAttributes(gltf, {semantics: ['POSITION']}); + expect(gltf.buffers.buffer.byteLength + size).toEqual(buffer.length); + }); + + it('Doesn\'t quantize non-float attribute', function() { + var gltf = clone(testGltf); + gltf.buffers.buffer.extras._pipeline.source = buffer; + quantizeAttributes(gltf, {semantics: ['NORMAL']}); + expect(gltf.buffers.buffer.byteLength).toEqual(buffer.length); + }); + + it('Doesn\'t quantize already quantized attribute', function() { + var gltf = clone(testGltf); + gltf.buffers.buffer.extras._pipeline.source = buffer; + quantizeAttributes(gltf, {semantics: ['TEXCOORD']}); + expect(gltf.buffers.buffer.byteLength).toEqual(buffer.length); }); }); \ No newline at end of file From d0569ce8cbc7251e8d2b2ec56f830e0bd9a92c37 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Tue, 7 Jun 2016 16:51:08 -0400 Subject: [PATCH 20/36] Tweaks --- lib/quantizeAttributes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/quantizeAttributes.js b/lib/quantizeAttributes.js index 0ae072e0..e33de962 100644 --- a/lib/quantizeAttributes.js +++ b/lib/quantizeAttributes.js @@ -75,6 +75,7 @@ function quantizeAttributes(gltf, options) { } var semantics = options.semantics; if (defined(semantics)) { + validSemantics = {}; for (i = 0; i < semantics.length; i++) { var semantic = semantics[i]; validSemantics[semantic] = true; From 6a17276a0e2f8bb936e49fc65ed550badefa3799 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Tue, 7 Jun 2016 16:51:54 -0400 Subject: [PATCH 21/36] Removed quantized data directory - it isn't needed --- specs/data/quantized/Box.bin | Bin 648 -> 0 bytes specs/data/quantized/Box.gltf | 240 -------------------- specs/data/quantized/Box0FS.glsl | 17 -- specs/data/quantized/Box0VS.glsl | 12 - specs/data/quantized/Duck.gltf | 362 ------------------------------- 5 files changed, 631 deletions(-) delete mode 100644 specs/data/quantized/Box.bin delete mode 100644 specs/data/quantized/Box.gltf delete mode 100644 specs/data/quantized/Box0FS.glsl delete mode 100644 specs/data/quantized/Box0VS.glsl delete mode 100644 specs/data/quantized/Duck.gltf diff --git a/specs/data/quantized/Box.bin b/specs/data/quantized/Box.bin deleted file mode 100644 index 29a29e1385f5c15edd4d0e456c375f61a13c7fd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 648 zcmb7;0SdxE3 Date: Tue, 7 Jun 2016 17:07:23 -0400 Subject: [PATCH 22/36] Fixes to precision and test --- lib/quantizeAttributes.js | 2 +- specs/lib/quantizeAttributesSpec.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/quantizeAttributes.js b/lib/quantizeAttributes.js index e33de962..091d0495 100644 --- a/lib/quantizeAttributes.js +++ b/lib/quantizeAttributes.js @@ -71,7 +71,7 @@ function quantizeAttributes(gltf, options) { var validSemantics; if (defined(options)) { if (defined(options.precision)) { - precision = 10^options.precision; + precision = Math.pow(10, options.precision); } var semantics = options.semantics; if (defined(semantics)) { diff --git a/specs/lib/quantizeAttributesSpec.js b/specs/lib/quantizeAttributesSpec.js index 6c1a82d6..45653cd7 100644 --- a/specs/lib/quantizeAttributesSpec.js +++ b/specs/lib/quantizeAttributesSpec.js @@ -125,6 +125,16 @@ describe('quantizeAttributes', function() { expect(gltf.buffers.buffer.byteLength + size).toEqual(buffer.length); }); + it('Reduces the decimal places in decode matrix using options.precision', function() { + var gltf = clone(testGltf); + gltf.buffers.buffer.extras._pipeline.source = buffer; + var precision = 6; + quantizeAttributes(gltf, {precision: precision}); + var matrixEntry = '' + gltf.accessors.accessor_0.extensions.WEB3D_quantized_attributes.decodeMatrix[0]; + var calculatedPrecision = matrixEntry.substring(matrixEntry.indexOf('.')).length; + expect(precision).toEqual(calculatedPrecision); + }); + it('Doesn\'t quantize non-float attribute', function() { var gltf = clone(testGltf); gltf.buffers.buffer.extras._pipeline.source = buffer; From 466a802d55ca02671bb1b1789c51399228fa2b3e Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Wed, 8 Jun 2016 16:57:52 -0400 Subject: [PATCH 23/36] Updates --- lib/byteLengthForComponentType.js | 12 +-- lib/quantizeAttributes.js | 2 +- lib/uninterleaveAndPackBuffers.js | 140 ++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 6 deletions(-) create mode 100644 lib/uninterleaveAndPackBuffers.js diff --git a/lib/byteLengthForComponentType.js b/lib/byteLengthForComponentType.js index 80bffc7e..5fe83c65 100644 --- a/lib/byteLengthForComponentType.js +++ b/lib/byteLengthForComponentType.js @@ -1,4 +1,6 @@ 'use strict'; +var Cesium = require('cesium'); +var WebGLConstants = Cesium.WebGLConstants; module.exports = byteLengthForComponentType; @@ -16,13 +18,13 @@ module.exports = byteLengthForComponentType; */ function byteLengthForComponentType(componentType) { switch (componentType) { - case 5120: - case 5121: + case WebGLConstants.BYTE: + case WebGLConstants.UNSIGNED_BYTE: return 1; - case 5122: - case 5123: + case WebGLConstants.SHORT: + case WebGLConstants.UNSIGNED_SHORT: return 2; - case 5126: + case WebGLConstants.FLOAT: return 4; } } \ No newline at end of file diff --git a/lib/quantizeAttributes.js b/lib/quantizeAttributes.js index 091d0495..ea55cf64 100644 --- a/lib/quantizeAttributes.js +++ b/lib/quantizeAttributes.js @@ -6,7 +6,7 @@ var addExtensionsUsed = require('./addExtensionsUsed'); var byteLengthForComponentType = require('./byteLengthForComponentType'); var getAccessorByteStride = require('./getAccessorByteStride'); var numberOfComponentsForType = require('./numberOfComponentsForType'); -var packBuffers = require('./packBuffers'); +var uninterleaveAndPackBuffers = require('./uninterleaveAndPackBuffers'); module.exports = quantizeAttributes; diff --git a/lib/uninterleaveAndPackBuffers.js b/lib/uninterleaveAndPackBuffers.js new file mode 100644 index 00000000..80be8fea --- /dev/null +++ b/lib/uninterleaveAndPackBuffers.js @@ -0,0 +1,140 @@ +'use strict'; +var Cesium = require('cesium'); +var WebGLConstants = Cesium.WebGLConstants; +var defined = Cesium.defined; +var defaultValue = Cesium.defaultValue; + +var byteLengthForComponentType = require('./byteLengthForComponentType'); +var getAccessorByteStride = require('./getAccessorByteStride'); +var numberOfComponentsForType = require('./numberOfComponentsForType'); + +module.exports = packBuffers; + +/** + * Repacks the accessed buffer data into contiguous chunks. + * Also has the effect of un-interleaving interleaved accessors. + * + * @param gltf + */ +function packBuffers(gltf) { + var buffers = gltf.buffers; + var packBufferViews = {length : 0}; + for (var bufferId in buffers) { + if (buffers.hasOwnProperty(bufferId)) { + packGltfBuffer(gltf, bufferId, packBufferViews); + } + } + delete packBufferViews.length; + gltf.bufferViews = packBufferViews; +} + +function packGltfBuffer(gltf, bufferId, packBufferViews) { + var buffers = gltf.buffers; + var buffer = buffers[bufferId]; + var source = buffer.extras._pipeline.source; + var packBuffer = new Uint8Array(source.length); + var accessors = getAccessorsByTargetAndByteLength(gltf, bufferId); + var offset = 0; + + var targets = [WebGLConstants.ARRAY_BUFFER, 0, WebGLConstants.ELEMENT_ARRAY_BUFFER]; + for (var i = 0; i < targets.length; i++) { + var target = targets[i]; + var accessorsByByteLength = accessors[target]; + if (defined(accessorsByByteLength)) { + offset = packAccessorsForTarget(gltf, bufferId, target, accessorsByByteLength, source, packBuffer, packBufferViews, offset); + } + } + buffer.extras._pipeline.source = new Buffer(new Uint8Array(packBuffer.buffer, 0, offset)); + buffer.byteLength = offset; +} + +function packAccessorsForTarget(gltf, bufferId, target, accessorsByByteLength, sourceBuffer, packBuffer, packBufferViews, offset) { + var bufferViewId = 'bufferView_' + packBufferViews.length; + var originalOffset = packOffset; + var byteLengths = [4, 2, 1]; + var packOffset = 0; + for (var i = 0; i < byteLengths.length; i++) { + var byteLength = byteLengths[i]; + var accessorIds = accessorsByByteLength[byteLength]; + if (defined(accessorIds) && accessorIds.length > 0) { + // Byte-boundary align the offset if it isn't already + packOffset += packOffset % byteLength; + packOffset = packAccessors(gltf, bufferId, target, accessorIds, sourceBuffer, packBuffer, bufferViewId, packOffset); + } + } + var packBufferView = { + buffer : bufferId, + byteLength : packOffset - originalOffset, + byteOffset : originalOffset, + extras : { + _pipeline : { + deleteExtras : true + } + } + }; + gltf.bufferViews[bufferViewId] = packBufferView; + if (target > 0) { + packBufferView.target = target; + } + packBufferViews[bufferViewId] = packBufferView; + packBufferViews.length++; + return offset + packOffset; +} + +function packAccessors(gltf, bufferId, target, accessorsToPack, sourceBuffer, packBuffer, bufferViewId, packOffset) { + var accessors = gltf.accessors; + var bufferViews = gltf.bufferViews; + var length = accessorsToPack.length; + var bytesWritten = 0; + for (var i = 0; i < length; i++) { + var accessorId = accessorsToPack[i]; + var accessor = accessors[accessorId]; + var bufferView = bufferViews[accessor.bufferView]; + var byteStride = getAccessorByteStride(accessor); + var byteOffset = accessor.byteOffset + bufferView.byteOffset; + var numberOfComponents = numberOfComponentsForType(accessor.type); + var componentByteLength = byteLengthForComponentType(accessor.componentType); + var byteLength = numberOfComponents * componentByteLength; + var offset = byteOffset; + var count = accessor.count; + accessor.byteStride = 0; + accessor.bufferView = bufferViewId; + accessor.byteOffset = packOffset + bytesWritten; + for (var num = 0; num < count; num++) { + for (var j = 0; j < byteLength; j++) { + packBuffer[packOffset + bytesWritten] = sourceBuffer[offset + j]; + bytesWritten++; + } + offset += byteStride; + } + } + return packOffset + bytesWritten; +} + +function getAccessorsByTargetAndByteLength(gltf, bufferId) { + var accessors = gltf.accessors; + var bufferViews = gltf.bufferViews; + var accessorsByTargetAndByteLength = {}; + for (var accessorId in accessors) { + if (accessors.hasOwnProperty(accessorId)) { + var accessor = accessors[accessorId]; + var bufferView = bufferViews[accessor.bufferView]; + if (bufferView.buffer === bufferId) { + var target = defaultValue(bufferView.target, 0); + var accessorsByByteLength = accessorsByTargetAndByteLength[target]; + if (!defined(accessorsByByteLength)) { + accessorsByByteLength = {}; + accessorsByTargetAndByteLength[target] = accessorsByByteLength; + } + var byteLength = byteLengthForComponentType(accessor.componentType); + var accessorIds = accessorsByByteLength[byteLength]; + if (!defined(accessorIds)) { + accessorIds = []; + accessorsByByteLength[byteLength] = accessorIds; + } + accessorIds.push(accessorId); + } + } + } + return accessorsByTargetAndByteLength; +} \ No newline at end of file From d0c4214eb0f7cc7ccd6e0fb77cd012114f007618 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Wed, 8 Jun 2016 17:02:13 -0400 Subject: [PATCH 24/36] Updated packBuffers references --- lib/quantizeAttributes.js | 2 +- specs/lib/uninterleaveAndPackBuffersSpec.js | 118 ++++++++++++++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 specs/lib/uninterleaveAndPackBuffersSpec.js diff --git a/lib/quantizeAttributes.js b/lib/quantizeAttributes.js index ea55cf64..a786dd79 100644 --- a/lib/quantizeAttributes.js +++ b/lib/quantizeAttributes.js @@ -139,7 +139,7 @@ function quantizeAttributes(gltf, options) { } if (isQuantized) { // Repack the buffers - packBuffers(gltf); + uninterleaveAndPackBuffers(gltf); // Finalize addExtensionsUsed(gltf, 'WEB3D_quantized_attributes'); } diff --git a/specs/lib/uninterleaveAndPackBuffersSpec.js b/specs/lib/uninterleaveAndPackBuffersSpec.js new file mode 100644 index 00000000..7e32f8bc --- /dev/null +++ b/specs/lib/uninterleaveAndPackBuffersSpec.js @@ -0,0 +1,118 @@ +'use strict'; +var clone = require('clone'); + +var byteLengthForComponentType = require('../../lib/byteLengthForComponentType'); +var numberOfComponentsForType = require('../../lib/numberOfComponentsForType'); +var packBuffers = require('../../lib/uninterleaveAndPackBuffers'); + +describe('uninterleaveAndPackBuffers', function() { + var buffer = new Uint8Array(96); + var testGltf = { + accessors : { + // Interleaved accessors in bufferView_0 + accessor_0 : { + bufferView : 'bufferView_0', + byteOffset : 0, + byteStride : 18, + componentType : 5126, + count : 3, + type : 'VEC3' + }, + accessor_1 : { + bufferView : 'bufferView_0', + byteOffset : 12, + byteStride : 18, + componentType : 5123, + count : 3, + type : 'VEC2' + }, + // Block accessors in bufferView_1 + accessor_2 : { + bufferView : 'bufferView_1', + byteOffset : 0, + byteStride : 12, + componentType : 5126, + count : 3, + type : 'VEC3' + }, + accessor_3 : { + bufferView : 'bufferView_1', + byteOffset : 36, + componentType : 5123, + count : 3, + type : 'VEC2' + } + }, + bufferViews : { + bufferView_0 : { + buffer : 'buffer', + byteLength : 48, + byteOffset : 0, + target : 34962 + }, + bufferView_1 : { + buffer : 'buffer', + byteLength : 48, + byteOffset : 48, + target : 34963 + } + }, + buffers : { + buffer : { + byteLength : buffer.length, + type : 'arraybuffer', + extras : { + _pipeline : {} + } + } + } + }; + + it('doesn\'t remove any data if the whole buffer is used', function() { + var gltf = clone(testGltf); + gltf.buffers.buffer.extras._pipeline.source = buffer; + packBuffers(gltf); + expect(gltf.buffers.buffer.byteLength).toEqual(testGltf.buffers.buffer.byteLength); + }); + + it('removes extra trailing data on the buffer', function() { + var gltf = clone(testGltf); + gltf.buffers.buffer.extras._pipeline.source = new Uint8Array(buffer.length * 2); + packBuffers(gltf); + expect(gltf.buffers.buffer.byteLength).toEqual(testGltf.buffers.buffer.byteLength); + }); + + it('removes interleaved unused data', function() { + var gltf = clone(testGltf); + gltf.buffers.buffer.extras._pipeline.source = buffer; + var deletedAccessorId = 'accessor_1'; + var deletedAccessor = gltf.accessors[deletedAccessorId]; + var size = byteLengthForComponentType(deletedAccessor.componentType) * numberOfComponentsForType(deletedAccessor.type) * deletedAccessor.count; + delete gltf.accessors[deletedAccessorId]; + packBuffers(gltf); + expect(gltf.buffers.buffer.byteLength + size).toEqual(testGltf.buffers.buffer.byteLength); + }); + + it('removes block unused data', function() { + var gltf = clone(testGltf); + gltf.buffers.buffer.extras._pipeline.source = buffer; + var deletedAccessorId = 'accessor_2'; + var deletedAccessor = gltf.accessors[deletedAccessorId]; + var size = byteLengthForComponentType(deletedAccessor.componentType) * numberOfComponentsForType(deletedAccessor.type) * deletedAccessor.count; + delete gltf.accessors[deletedAccessorId]; + packBuffers(gltf); + expect(gltf.buffers.buffer.byteLength + size).toEqual(testGltf.buffers.buffer.byteLength); + }); + + it('removes unused bufferView', function() { + var gltf = clone(testGltf); + gltf.buffers.buffer.extras._pipeline.source = buffer; + var size = gltf.bufferViews.bufferView_0.byteLength; + delete gltf.accessors.accessor_0; + delete gltf.accessors.accessor_1; + packBuffers(gltf); + expect(gltf.buffers.buffer.byteLength + size).toEqual(testGltf.buffers.buffer.byteLength); + var bufferViewCount = Object.keys(gltf.bufferViews).length; + expect(bufferViewCount).toEqual(1); + }); +}); \ No newline at end of file From f134385e56b8b7d79f4e9c1cc3861c0490ee6223 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Wed, 8 Jun 2016 17:02:36 -0400 Subject: [PATCH 25/36] Removed gltf from example header --- lib/quantizeAttributes.js | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/lib/quantizeAttributes.js b/lib/quantizeAttributes.js index a786dd79..b46f182e 100644 --- a/lib/quantizeAttributes.js +++ b/lib/quantizeAttributes.js @@ -24,26 +24,6 @@ module.exports = quantizeAttributes; * @returns glTF with quantized attributes * * @example - * var gltf = { - * accessors: { - * accessor_0: {...} - * accessor_1: {...} - * }, - * meshes: { - * geometry: { - * name: 'Mesh', - * primitives: [ - * { - * attributes: { - * NORMAL: 'accessor_0', - * POSITION: 'accessor_1' - * } - * } - * ] - * } - * } - * }; - * * // Quantize all valid attributes * quantizeAttributes(gltf); * From be8790e17fc1ac739d7be5efe10f1e8952015879 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Wed, 8 Jun 2016 17:06:41 -0400 Subject: [PATCH 26/36] Use process.nextTick --- lib/getBinaryGltf.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/getBinaryGltf.js b/lib/getBinaryGltf.js index a5f1da51..90f363bb 100644 --- a/lib/getBinaryGltf.js +++ b/lib/getBinaryGltf.js @@ -101,7 +101,9 @@ function getBinaryGltf(gltf, callback) { var glb = Buffer.concat([header, scene, body], glbLength); if (callback) { - callback(header, scene, body); + process.nextTick(function () { + callback(header, scene, body); + }); } return glb; } \ No newline at end of file From e22877775f1ad171eca40a869f380c75a065f3cc Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Wed, 8 Jun 2016 17:09:53 -0400 Subject: [PATCH 27/36] Missed a change --- lib/uninterleaveAndPackBuffers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/uninterleaveAndPackBuffers.js b/lib/uninterleaveAndPackBuffers.js index 80be8fea..6af0d09c 100644 --- a/lib/uninterleaveAndPackBuffers.js +++ b/lib/uninterleaveAndPackBuffers.js @@ -8,7 +8,7 @@ var byteLengthForComponentType = require('./byteLengthForComponentType'); var getAccessorByteStride = require('./getAccessorByteStride'); var numberOfComponentsForType = require('./numberOfComponentsForType'); -module.exports = packBuffers; +module.exports = uninterleaveAndPackBuffers; /** * Repacks the accessed buffer data into contiguous chunks. @@ -16,7 +16,7 @@ module.exports = packBuffers; * * @param gltf */ -function packBuffers(gltf) { +function uninterleaveAndPackBuffers(gltf) { var buffers = gltf.buffers; var packBufferViews = {length : 0}; for (var bufferId in buffers) { From 09d39da058ad37d211dc2f86fea761b192381485 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Wed, 8 Jun 2016 17:27:15 -0400 Subject: [PATCH 28/36] See if re-ordering requires fixes CI failure --- lib/quantizeAttributes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/quantizeAttributes.js b/lib/quantizeAttributes.js index b46f182e..aacccfb4 100644 --- a/lib/quantizeAttributes.js +++ b/lib/quantizeAttributes.js @@ -3,10 +3,10 @@ var Cesium = require('cesium'); var defined = Cesium.defined; var addExtensionsUsed = require('./addExtensionsUsed'); +var uninterleaveAndPackBuffers = require('./uninterleaveAndPackBuffers'); var byteLengthForComponentType = require('./byteLengthForComponentType'); var getAccessorByteStride = require('./getAccessorByteStride'); var numberOfComponentsForType = require('./numberOfComponentsForType'); -var uninterleaveAndPackBuffers = require('./uninterleaveAndPackBuffers'); module.exports = quantizeAttributes; From deb1c540289d6707898c2bb3ce74eb39e0375c0b Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Wed, 8 Jun 2016 18:34:31 -0400 Subject: [PATCH 29/36] Updated travis node to v4 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 885c266c..36c5f62e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ language: node_js node_js: - - 0.12 + - 4.0.0 after_success: 'npm run coveralls' \ No newline at end of file From 5d7e99f858d7364097f9142d3fc5485b909688ec Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Wed, 8 Jun 2016 18:36:20 -0400 Subject: [PATCH 30/36] Pulled tweaks from removeUnusedAttributes branch --- lib/uninterleaveAndPackBuffers.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/uninterleaveAndPackBuffers.js b/lib/uninterleaveAndPackBuffers.js index 6af0d09c..10327979 100644 --- a/lib/uninterleaveAndPackBuffers.js +++ b/lib/uninterleaveAndPackBuffers.js @@ -50,7 +50,7 @@ function packGltfBuffer(gltf, bufferId, packBufferViews) { function packAccessorsForTarget(gltf, bufferId, target, accessorsByByteLength, sourceBuffer, packBuffer, packBufferViews, offset) { var bufferViewId = 'bufferView_' + packBufferViews.length; - var originalOffset = packOffset; + var originalOffset = offset; var byteLengths = [4, 2, 1]; var packOffset = 0; for (var i = 0; i < byteLengths.length; i++) { @@ -59,7 +59,7 @@ function packAccessorsForTarget(gltf, bufferId, target, accessorsByByteLength, s if (defined(accessorIds) && accessorIds.length > 0) { // Byte-boundary align the offset if it isn't already packOffset += packOffset % byteLength; - packOffset = packAccessors(gltf, bufferId, target, accessorIds, sourceBuffer, packBuffer, bufferViewId, packOffset); + packOffset = packAccessors(gltf, accessorIds, sourceBuffer, packBuffer, bufferViewId, packOffset); } } var packBufferView = { @@ -81,7 +81,7 @@ function packAccessorsForTarget(gltf, bufferId, target, accessorsByByteLength, s return offset + packOffset; } -function packAccessors(gltf, bufferId, target, accessorsToPack, sourceBuffer, packBuffer, bufferViewId, packOffset) { +function packAccessors(gltf, accessorsToPack, sourceBuffer, packBuffer, bufferViewId, packOffset) { var accessors = gltf.accessors; var bufferViews = gltf.bufferViews; var length = accessorsToPack.length; From 6071c9ee87e0a928f1069eb384cddb3dff72b2fd Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Thu, 9 Jun 2016 12:06:20 -0400 Subject: [PATCH 31/36] Added quantized option to command line --- bin/gltf-pipeline.js | 7 +++++-- lib/gltfPipeline.js | 5 +++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/bin/gltf-pipeline.js b/bin/gltf-pipeline.js index 8fa5d50d..c6b9df71 100644 --- a/bin/gltf-pipeline.js +++ b/bin/gltf-pipeline.js @@ -18,7 +18,8 @@ if (process.argv.length < 3 || defined(argv.h) || defined(argv.help)) { ' -i, input=PATH Read unoptimized glTF from the specified file.\n ' + ' -o, output=PATH write optimized glTF to the specified file.\n' + ' -b, write binary glTF file.\n' + - ' -s, writes out separate geometry/animation data files, shader files and textures instead of embedding them in the glTF file.\n '; + ' -s, writes out separate geometry/animation data files, shader files and textures instead of embedding them in the glTF file.\n' + ' -q, quantize the attributes of this model.\n'; process.stdout.write(help); return; } @@ -31,6 +32,7 @@ var filePath = path.dirname(gltfPath); var outputPath = defaultValue(argv._[1], argv.o); var isSeparate = defaultValue(argv.s, false); var isBinary = defaultValue(argv.b, false); +var isQuantized = defaultValue(argv.q, false); if (!defined(gltfPath)) { throw new DeveloperError('Input path is undefined.'); @@ -47,7 +49,8 @@ if (!defined(outputPath)) { var options = { isBinary : isBinary, - isEmbedded : !isSeparate + isEmbedded : !isSeparate, + isQuantized : isQuantized }; processFileToDisk(gltfPath, outputPath, options); diff --git a/lib/gltfPipeline.js b/lib/gltfPipeline.js index 09264ca8..4670e373 100644 --- a/lib/gltfPipeline.js +++ b/lib/gltfPipeline.js @@ -28,6 +28,7 @@ var writeBinaryGltf = require('./writeBinaryGltf'); var readGltf = require('./readGltf'); var removePipelineExtras = require('./removePipelineExtras'); var loadGltfUris = require('./loadGltfUris'); +var quantizeAttributes = require('./quantizeAttributes'); var Cesium = require('cesium'); var defaultValue = Cesium.defaultValue; var defined = Cesium.defined; @@ -72,6 +73,10 @@ function processJSONWithExtras(gltfWithExtras, options, callback) { //Run removeUnused stage again after all pipeline stages have been run to remove objects that become unused removeUnused(gltfWithExtras); + if (options.isQuantized) { + quantizeAttributes(gltfWithExtras); + } + callback(gltfWithExtras); } From 84dbc41cb099619058911ff3ec49cb3f63c35ad5 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Thu, 9 Jun 2016 14:03:20 -0400 Subject: [PATCH 32/36] jsHint fix --- bin/gltf-pipeline.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/gltf-pipeline.js b/bin/gltf-pipeline.js index c6b9df71..1640b419 100644 --- a/bin/gltf-pipeline.js +++ b/bin/gltf-pipeline.js @@ -18,7 +18,7 @@ if (process.argv.length < 3 || defined(argv.h) || defined(argv.help)) { ' -i, input=PATH Read unoptimized glTF from the specified file.\n ' + ' -o, output=PATH write optimized glTF to the specified file.\n' + ' -b, write binary glTF file.\n' + - ' -s, writes out separate geometry/animation data files, shader files and textures instead of embedding them in the glTF file.\n' + ' -s, writes out separate geometry/animation data files, shader files and textures instead of embedding them in the glTF file.\n' + ' -q, quantize the attributes of this model.\n'; process.stdout.write(help); return; From 24dca8bbef7e6d0ccef0eaeb296cb4f102f28a18 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Thu, 9 Jun 2016 15:22:26 -0400 Subject: [PATCH 33/36] Removed packBuffers --- lib/packBuffers.js | 132 --------------------------------------------- 1 file changed, 132 deletions(-) delete mode 100644 lib/packBuffers.js diff --git a/lib/packBuffers.js b/lib/packBuffers.js deleted file mode 100644 index ca9011c0..00000000 --- a/lib/packBuffers.js +++ /dev/null @@ -1,132 +0,0 @@ -'use strict'; -var Cesium = require('cesium'); -var defined = Cesium.defined; - -var byteLengthForComponentType = require('./byteLengthForComponentType'); -var getAccessorByteStride = require('./getAccessorByteStride'); -var numberOfComponentsForType = require('./numberOfComponentsForType'); - -module.exports = packBuffers; - -/** - * Repacks the accessed buffer data into contiguous chunks. - * Also has the effect of un-interleaving interleaved accessors. - * - * @param gltf - */ -function packBuffers(gltf) { - var buffers = gltf.buffers; - var packBufferViews = {length : 0}; - for (var bufferId in buffers) { - if (buffers.hasOwnProperty(bufferId)) { - packGltfBuffer(gltf, bufferId, packBufferViews); - } - } - delete packBufferViews.length; - gltf.bufferViews = packBufferViews; -} - -function packGltfBuffer(gltf, bufferId, packBufferViews) { - var buffers = gltf.buffers; - var buffer = buffers[bufferId]; - var source = buffer.extras._pipeline.source; - var packBuffer = new Uint8Array(source.length); - var accessors = getAccessorsByTargetAndByteLength(gltf, bufferId); - var offset = 0; - - var targets = [34962, 0, 34963]; - for (var i = 0; i < targets.length; i++) { - var target = targets[i]; - var accessorsByByteLength = accessors[target]; - if (defined(accessorsByByteLength)) { - offset = packAccessorsForTarget(gltf, bufferId, target, accessorsByByteLength, source, packBuffer, packBufferViews, offset); - } - } - buffer.extras._pipeline.source = new Uint8Array(packBuffer.buffer, 0, offset); - buffer.byteLength = offset; -} - -function packAccessorsForTarget(gltf, bufferId, target, accessorsByByteLength, sourceBuffer, packBuffer, packBufferViews, packOffset) { - var bufferViewId = 'bufferView_' + packBufferViews.length; - var originalOffset = packOffset; - var byteLengths = [4, 2, 1]; - for (var i = 0; i < byteLengths.length; i++) { - var byteLength = byteLengths[i]; - var accessorIds = accessorsByByteLength[byteLength]; - if (defined(accessorIds) && accessorIds.length > 0) { - // Byte-boundary align the offset if it isn't already - packOffset += packOffset % byteLength; - packOffset = packAccessors(gltf, bufferId, target, accessorIds, sourceBuffer, packBuffer, bufferViewId, packOffset); - } - } - var packBufferView = { - buffer : bufferId, - byteLength : packOffset - originalOffset, - byteOffset : originalOffset - }; - if (target > 0) { - packBufferView.target = target; - } - packBufferViews[bufferViewId] = packBufferView; - packBufferViews.length++; - return packOffset; -} - -function packAccessors(gltf, bufferId, target, accessorsToPack, sourceBuffer, packBuffer, bufferViewId, packOffset) { - var accessors = gltf.accessors; - var bufferViews = gltf.bufferViews; - var length = accessorsToPack.length; - var bytesWritten = 0; - for (var i = 0; i < length; i++) { - var accessorId = accessorsToPack[i]; - var accessor = accessors[accessorId]; - var bufferView = bufferViews[accessor.bufferView]; - var byteStride = getAccessorByteStride(accessor); - var byteOffset = accessor.byteOffset + bufferView.byteOffset; - var numberOfComponents = numberOfComponentsForType(accessor.type); - var componentByteLength = byteLengthForComponentType(accessor.componentType); - var byteLength = numberOfComponents * componentByteLength; - var offset = byteOffset; - var count = accessor.count; - accessor.byteStride = 0; - accessor.bufferView = bufferViewId; - accessor.byteOffset = bytesWritten; - for (var num = 0; num < count; num++) { - for (var j = 0; j < byteLength; j++) { - packBuffer[packOffset + bytesWritten] = sourceBuffer[offset + j]; - bytesWritten++; - } - offset += byteStride; - } - } - return packOffset + bytesWritten; -} - -function getAccessorsByTargetAndByteLength(gltf, bufferId) { - var accessors = gltf.accessors; - var bufferViews = gltf.bufferViews; - var accessorsByTargetAndByteLength = {}; - for (var accessorId in accessors) { - var accessor = accessors[accessorId]; - var bufferView = bufferViews[accessor.bufferView]; - if (bufferView.buffer === bufferId) { - var target = bufferView.target; - if (!defined(target)) { - target = 0; - } - var accessorsByByteLength = accessorsByTargetAndByteLength[target]; - if (!defined(accessorsByByteLength)) { - accessorsByByteLength = {}; - accessorsByTargetAndByteLength[target] = accessorsByByteLength; - } - var byteLength = byteLengthForComponentType(accessor.componentType); - var accessorIds = accessorsByByteLength[byteLength]; - if (!defined(accessorIds)) { - accessorIds = []; - accessorsByByteLength[byteLength] = accessorIds; - } - accessorIds.push(accessorId); - } - } - return accessorsByTargetAndByteLength; -} \ No newline at end of file From 74f46e888f00abd37fef785f015d664ada10fd52 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Thu, 9 Jun 2016 15:47:58 -0400 Subject: [PATCH 34/36] Removed old packBufferSpec file --- specs/lib/packBuffersSpec.js | 118 ----------------------------------- 1 file changed, 118 deletions(-) delete mode 100644 specs/lib/packBuffersSpec.js diff --git a/specs/lib/packBuffersSpec.js b/specs/lib/packBuffersSpec.js deleted file mode 100644 index daa3a14b..00000000 --- a/specs/lib/packBuffersSpec.js +++ /dev/null @@ -1,118 +0,0 @@ -'use strict'; -var clone = require('clone'); - -var byteLengthForComponentType = require('../../lib/byteLengthForComponentType'); -var numberOfComponentsForType = require('../../lib/numberOfComponentsForType'); -var packBuffers = require('../../lib/packBuffers'); - -describe('packBuffers', function() { - var buffer = new Uint8Array(96); - var testGltf = { - accessors : { - // Interleaved accessors in bufferView_0 - accessor_0 : { - bufferView : 'bufferView_0', - byteOffset : 0, - byteStride : 18, - componentType : 5126, - count : 3, - type : 'VEC3' - }, - accessor_1 : { - bufferView : 'bufferView_0', - byteOffset : 12, - byteStride : 18, - componentType : 5123, - count : 3, - type : 'VEC2' - }, - // Block accessors in bufferView_1 - accessor_2 : { - bufferView : 'bufferView_1', - byteOffset : 0, - byteStride : 12, - componentType : 5126, - count : 3, - type : 'VEC3' - }, - accessor_3 : { - bufferView : 'bufferView_1', - byteOffset : 36, - componentType : 5123, - count : 3, - type : 'VEC2' - } - }, - bufferViews : { - bufferView_0 : { - buffer : 'buffer', - byteLength : 48, - byteOffset : 0, - target : 34962 - }, - bufferView_1 : { - buffer : 'buffer', - byteLength : 48, - byteOffset : 48, - target : 34963 - } - }, - buffers : { - buffer : { - byteLength : buffer.length, - type : 'arraybuffer', - extras : { - _pipeline : {} - } - } - } - }; - - it('doesn\'t remove any data if the whole buffer is used', function() { - var gltf = clone(testGltf); - gltf.buffers.buffer.extras._pipeline.source = buffer; - packBuffers(gltf); - expect(gltf.buffers.buffer.byteLength).toEqual(testGltf.buffers.buffer.byteLength); - }); - - it('removes extra trailing data on the buffer', function() { - var gltf = clone(testGltf); - gltf.buffers.buffer.extras._pipeline.source = new Uint8Array(buffer.length * 2); - packBuffers(gltf); - expect(gltf.buffers.buffer.byteLength).toEqual(testGltf.buffers.buffer.byteLength); - }); - - it('removes interleaved unused data', function() { - var gltf = clone(testGltf); - gltf.buffers.buffer.extras._pipeline.source = buffer; - var deletedAccessorId = 'accessor_1'; - var deletedAccessor = gltf.accessors[deletedAccessorId]; - var size = byteLengthForComponentType(deletedAccessor.componentType) * numberOfComponentsForType(deletedAccessor.type) * deletedAccessor.count; - delete gltf.accessors[deletedAccessorId]; - packBuffers(gltf); - expect(gltf.buffers.buffer.byteLength + size).toEqual(testGltf.buffers.buffer.byteLength); - }); - - it('removes block unused data', function() { - var gltf = clone(testGltf); - gltf.buffers.buffer.extras._pipeline.source = buffer; - var deletedAccessorId = 'accessor_2'; - var deletedAccessor = gltf.accessors[deletedAccessorId]; - var size = byteLengthForComponentType(deletedAccessor.componentType) * numberOfComponentsForType(deletedAccessor.type) * deletedAccessor.count; - delete gltf.accessors[deletedAccessorId]; - packBuffers(gltf); - expect(gltf.buffers.buffer.byteLength + size).toEqual(testGltf.buffers.buffer.byteLength); - }); - - it('removes unused bufferView', function() { - var gltf = clone(testGltf); - gltf.buffers.buffer.extras._pipeline.source = buffer; - var size = gltf.bufferViews.bufferView_0.byteLength; - delete gltf.accessors.accessor_0; - delete gltf.accessors.accessor_1; - packBuffers(gltf); - expect(gltf.buffers.buffer.byteLength + size).toEqual(testGltf.buffers.buffer.byteLength); - var bufferViewCount = Object.keys(gltf.bufferViews).length; - expect(bufferViewCount).toEqual(1); - }); -}); \ No newline at end of file From 539fc232deceab85670bea5bc32485d018199561 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Fri, 10 Jun 2016 09:21:35 -0400 Subject: [PATCH 35/36] Make sure SCALAR is supported --- lib/quantizeAttributes.js | 38 ++++++++++++++++------------- specs/lib/quantizeAttributesSpec.js | 31 +++++++++++++++++++---- 2 files changed, 47 insertions(+), 22 deletions(-) diff --git a/lib/quantizeAttributes.js b/lib/quantizeAttributes.js index aacccfb4..f3797342 100644 --- a/lib/quantizeAttributes.js +++ b/lib/quantizeAttributes.js @@ -127,7 +127,9 @@ function quantizeAttributes(gltf, options) { function createDecodeMatrix(min, max, range) { var size = min.length + 1; - if (size === 3) { + if (size === 2) { + return createDecodeMatrix2(min, max, range); + } else if (size === 3) { return createDecodeMatrix3(min, max, range); } else if (size === 4) { return createDecodeMatrix4(min, max, range); @@ -136,6 +138,12 @@ function createDecodeMatrix(min, max, range) { } } +function createDecodeMatrix2(min, max, range) { + return [(max[0] - min[0])/range, 0.0, + min[0], 1.0 + ]; +} + function createDecodeMatrix3(min, max, range) { return [(max[0] - min[0])/range, 0.0, 0.0, 0.0, (max[1] - min[1])/range, 0.0, @@ -177,15 +185,13 @@ function getAllQuantizableAttributes(gltf, validSemantics) { for (var attribute in attributes) { if (attributes.hasOwnProperty(attribute)) { if (!defined(validSemantics) || defined(validSemantics[attribute])) { - if (attributeIsQuantizable(attribute)) { - var accessorId = attributes[attribute]; - if (!defined(visitedAccessors[accessorId])) { - var accessor = accessors[accessorId]; - if (accessorIsQuantizable(accessor)) { - accessorAttributes[accessorId] = attribute; - } - visitedAccessors[accessorId] = true; + var accessorId = attributes[attribute]; + if (!defined(visitedAccessors[accessorId])) { + var accessor = accessors[accessorId]; + if (accessorIsQuantizable(accessor)) { + accessorAttributes[accessorId] = attribute; } + visitedAccessors[accessorId] = true; } } } @@ -198,13 +204,11 @@ function getAllQuantizableAttributes(gltf, validSemantics) { return accessorAttributes; } -function attributeIsQuantizable(attributeSemantic) { - return attributeSemantic.indexOf("NORMAL") >= 0 || - attributeSemantic.indexOf("POSITION") >= 0 || - attributeSemantic.indexOf("TEXCOORD") >= 0 || - attributeSemantic.indexOf("JOINT") >= 0 || - attributeSemantic.indexOf("WEIGHT") >= 0 || - attributeSemantic.indexOf("COLOR") >= 0; +function typeIsQuantizable(type) { + return type === 'SCALAR' || + type === 'VEC2' || + type === 'VEC3' || + type === 'VEC4'; } function accessorIsQuantizable(accessor) { @@ -213,7 +217,7 @@ function accessorIsQuantizable(accessor) { return false; } // Only 32-bit float to 16-bit int quantization is supported - return accessor.componentType == 5126; + return accessor.componentType == 5126 && typeIsQuantizable(accessor.type); } function accessorIsQuantized(accessor) { diff --git a/specs/lib/quantizeAttributesSpec.js b/specs/lib/quantizeAttributesSpec.js index 45653cd7..b2468c2a 100644 --- a/specs/lib/quantizeAttributesSpec.js +++ b/specs/lib/quantizeAttributesSpec.js @@ -6,7 +6,7 @@ var numberOfComponentsForType = require('../../lib/numberOfComponentsForType'); var quantizeAttributes = require('../../lib/quantizeAttributes'); describe('quantizeAttributes', function() { - var buffer = new Buffer(new Uint8Array(108)); + var buffer = new Buffer(new Uint8Array(120)); var testGltf = { accessors : { // Interleaved accessors in bufferView_0 @@ -47,8 +47,8 @@ describe('quantizeAttributes', function() { byteOffset : 36, componentType : 5126, count : 3, - min : [0, 0, 0], - max : [65535, 65535, 65535], + min : [0, 0], + max : [65535, 65535], type : 'VEC2', extensions : { WEB3D_quantized_attributes : { @@ -61,6 +61,16 @@ describe('quantizeAttributes', function() { decodeMax : [1.0, 1.0] } } + }, + // SCALAR attribute + accessor_4 : { + bufferView : 'bufferView_1', + byteOffset : 60, + componentType : 5126, + count : 3, + min : [0], + max : [1], + type : 'SCALAR' } }, bufferViews : { @@ -72,7 +82,7 @@ describe('quantizeAttributes', function() { }, bufferView_1 : { buffer : 'buffer', - byteLength : 60, + byteLength : 72, byteOffset : 48, target : 34962 } @@ -98,7 +108,8 @@ describe('quantizeAttributes', function() { { attributes : { POSITION : 'accessor_2', - TEXCOORD : 'accessor_3' + TEXCOORD : 'accessor_3', + SCALAR_TEST : 'accessor_4' } } ] @@ -148,4 +159,14 @@ describe('quantizeAttributes', function() { quantizeAttributes(gltf, {semantics: ['TEXCOORD']}); expect(gltf.buffers.buffer.byteLength).toEqual(buffer.length); }); + + it('Quantizes scalar attribute', function() { + var gltf = clone(testGltf); + var accessor_4 = gltf.accessors.accessor_4; + var size = byteLengthForComponentType(accessor_4.componentType) * numberOfComponentsForType(accessor_4.type) * accessor_4.count; + size = size/2.0; + gltf.buffers.buffer.extras._pipeline.source = buffer; + quantizeAttributes(gltf, {semantics: ['SCALAR_TEST']}); + expect(gltf.buffers.buffer.byteLength + size).toEqual(buffer.length); + }); }); \ No newline at end of file From 05d8ad57e24eb77b7e0cd25f18cc69d577c1d9c7 Mon Sep 17 00:00:00 2001 From: Robert Taglang Date: Fri, 10 Jun 2016 09:22:52 -0400 Subject: [PATCH 36/36] Updated README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 47164535..bc472207 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Include `build/gltf-pipeline.js` it with a `script` tag. For a simple example, |`-o`|Directory or filename for the exported glTF file.|No| |`-b`|Write binary glTF file.|No, default `false`| |`-s`|Writes out separate geometry/animation data files, shader files and textures instead of embedding them in the glTF file.|No, default `false`| +|`-q`|Quantize attributes using WEB3D_quantized_attributes extension.|No, default `false`| |`-h`|Display help|No| ## Build Instructions