diff --git a/CHANGES.md b/CHANGES.md index e704ab10..4723615b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,10 @@ Change Log ========== +### 2.?.? - 2020-??-?? + +* Fixed writing duplicate resource that are referenced multiple times. [#483](https://github.com/AnalyticalGraphicsInc/gltf-pipeline/pull/483) + ### 2.1.8 - 2020-03-04 * Fixed a bug in `processGltf` and `gltfToGlb` where the user's `options` object was getting modified with internal options. [#528](https://github.com/CesiumGS/gltf-pipeline/pull/528) diff --git a/lib/readResources.js b/lib/readResources.js index 52022cf0..c19c692a 100644 --- a/lib/readResources.js +++ b/lib/readResources.js @@ -64,27 +64,27 @@ function readResources(gltf, options) { } function readBuffer(gltf, buffer, options) { - return readResource(gltf, buffer, options) + return readResource(gltf, buffer, false, options) .then(function(data) { buffer.extras._pipeline.source = data; }); } function readImage(gltf, image, options) { - return readResource(gltf, image, options) + return readResource(gltf, image, true, options) .then(function(data) { image.extras._pipeline.source = data; }); } function readShader(gltf, shader, options) { - return readResource(gltf, shader, options) + return readResource(gltf, shader, true, options) .then(function(data) { shader.extras._pipeline.source = data.toString(); }); } -function readResource(gltf, object, options) { +function readResource(gltf, object, saveResourceId, options) { const uri = object.uri; delete object.uri; // Don't hold onto the uri, its contents will be stored in extras._pipeline.source @@ -98,19 +98,22 @@ function readResource(gltf, object, options) { if (defined(extensions)) { const khrBinaryGltf = extensions.KHR_binary_glTF; if (defined(khrBinaryGltf)) { - return Promise.resolve(readBufferView(gltf, khrBinaryGltf.bufferView)); + return Promise.resolve(readBufferView(gltf, khrBinaryGltf.bufferView, object, saveResourceId)); } } if (defined(object.bufferView)) { - return Promise.resolve(readBufferView(gltf, object.bufferView)); + return Promise.resolve(readBufferView(gltf, object.bufferView, object, saveResourceId)); } if (isDataUri(uri)) { return Promise.resolve(dataUriToBuffer(uri)); } - return readFile(object, uri, options); + return readFile(object, uri, saveResourceId, options); } -function readBufferView(gltf, bufferViewId) { +function readBufferView(gltf, bufferViewId, object, saveResourceId) { + if (saveResourceId) { + object.extras._pipeline.resourceId = bufferViewId; + } const bufferView = gltf.bufferViews[bufferViewId]; const buffer = gltf.buffers[bufferView.buffer]; const source = buffer.extras._pipeline.source; @@ -118,7 +121,7 @@ function readBufferView(gltf, bufferViewId) { return source.slice(byteOffset, byteOffset + bufferView.byteLength); } -function readFile(object, uri, options) { +function readFile(object, uri, saveResourceId, options) { const resourceDirectory = options.resourceDirectory; const hasResourceDirectory = defined(resourceDirectory); @@ -140,6 +143,10 @@ function readFile(object, uri, options) { object.name = path.basename(relativePath, extension); } + if (saveResourceId) { + object.extras._pipeline.resourceId = absolutePath; + } + object.extras._pipeline.absolutePath = absolutePath; object.extras._pipeline.relativePath = relativePath; return fsExtra.readFile(absolutePath); diff --git a/lib/writeResources.js b/lib/writeResources.js index 5ee5e901..5a21753d 100644 --- a/lib/writeResources.js +++ b/lib/writeResources.js @@ -46,32 +46,35 @@ function writeResources(gltf, options) { options.separateShaders = defaultValue(options.separateShaders, false); options.dataUris = defaultValue(options.dataUris, false); + // Remember which of the resources have been written, so we can re-use them. + const writtenResourceMap = {}; + ForEach.image(gltf, function(image, i) { - writeImage(gltf, image, i, options); + writeImage(gltf, image, i, writtenResourceMap, options); ForEach.compressedImage(image, function(compressedImage) { - writeImage(gltf, compressedImage, i, options); + writeImage(gltf, compressedImage, i, writtenResourceMap, options); }); }); ForEach.shader(gltf, function(shader, i) { - writeShader(gltf, shader, i, options); + writeShader(gltf, shader, i, writtenResourceMap, options); }); // Buffers need to be written last because images and shaders may write to new buffers - removeUnusedElements(gltf, ['acessor', 'bufferView', 'buffer']); + removeUnusedElements(gltf, ['accessor', 'bufferView', 'buffer']); mergeBuffers(gltf, options.name); ForEach.buffer(gltf, function(buffer, bufferId) { - writeBuffer(gltf, buffer, bufferId, options); + writeBuffer(gltf, buffer, bufferId, writtenResourceMap, options); }); return gltf; } -function writeBuffer(gltf, buffer, i, options) { +function writeBuffer(gltf, buffer, i, writtenResourceMap, options) { if (defined(options.bufferStorage) && !options.separateBuffers) { writeBufferStorage(buffer, options); } else { - writeResource(gltf, buffer, i, options.separateBuffers, true, '.bin', options); + writeResource(gltf, buffer, i, options.separateBuffers, true, '.bin', writtenResourceMap, options); } } @@ -82,26 +85,26 @@ function writeBufferStorage(buffer, options) { options.bufferStorage.buffer = combinedBuffer; } -function writeImage(gltf, image, i, options) { +function writeImage(gltf, image, i, writtenResourceMap, options) { const extension = getImageExtension(image.extras._pipeline.source); - writeResource(gltf, image, i, options.separateTextures, options.dataUris, extension, options); + writeResource(gltf, image, i, options.separateTextures, options.dataUris, extension, writtenResourceMap, options); if (defined(image.bufferView)) { // Preserve the image mime type when writing to a buffer view image.mimeType = mime.getType(extension); } } -function writeShader(gltf, shader, i, options) { - writeResource(gltf, shader, i, options.separateShaders, options.dataUris, '.glsl', options); +function writeShader(gltf, shader, i, writtenResourceMap, options) { + writeResource(gltf, shader, i, options.separateShaders, options.dataUris, '.glsl', writtenResourceMap, options); } -function writeResource(gltf, object, index, separate, dataUris, extension, options) { +function writeResource(gltf, object, index, separate, dataUris, extension, writtenResourceMap, options) { if (separate) { - writeFile(gltf, object, index, extension, options); + writeFile(gltf, object, index, extension, writtenResourceMap, options); } else if (dataUris) { writeDataUri(object, extension); } else { - writeBufferView(gltf, object); + writeBufferView(gltf, object, writtenResourceMap); } } @@ -112,13 +115,26 @@ function writeDataUri(object, extension) { object.uri = 'data:' + mimeType + ';base64,' + source.toString('base64'); } -function writeBufferView(gltf, object) { +function writeBufferView(gltf, object, writtenResourceMap) { delete object.uri; + + // If we've written this resource before, re-use the bufferView + const resourceId = object.extras._pipeline.resourceId; + if (defined(resourceId) && defined(writtenResourceMap[resourceId])) { + object.bufferView = writtenResourceMap[resourceId]; + return; + } + let source = object.extras._pipeline.source; if (typeof source === 'string') { source = Buffer.from(source); } object.bufferView = addBuffer(gltf, source); + + // Save the bufferView so we can re-use it later + if (defined(resourceId)) { + writtenResourceMap[resourceId] = object.bufferView; + } } function getProgram(gltf, shaderIndex) { @@ -183,12 +199,25 @@ function getRelativePath(gltf, object, index, extension, options) { return relativePath; } -function writeFile(gltf, object, index, extension, options) { +function writeFile(gltf, object, index, extension, writtenResourceMap, options) { delete object.bufferView; + + // If we've written this resource before, re-use the uri + const resourceId = object.extras._pipeline.resourceId; + if (defined(resourceId) && defined(writtenResourceMap[resourceId])) { + object.uri = writtenResourceMap[resourceId]; + return; + } + const source = object.extras._pipeline.source; const relativePath = getRelativePath(gltf, object, index, extension, options); object.uri = relativePath; if (defined(options.separateResources)) { options.separateResources[relativePath] = source; } + + // Save the uri so we can re-use it later + if (defined(resourceId)) { + writtenResourceMap[resourceId] = object.uri; + } } diff --git a/specs/data/2.0/box-shared-image-references-separate/CesiumTexturedBoxTest0FS.glsl b/specs/data/2.0/box-shared-image-references-separate/CesiumTexturedBoxTest0FS.glsl new file mode 100644 index 00000000..782e1f41 --- /dev/null +++ b/specs/data/2.0/box-shared-image-references-separate/CesiumTexturedBoxTest0FS.glsl @@ -0,0 +1,18 @@ +precision highp float; +varying vec3 v_normal; +varying vec2 v_texcoord0; +uniform sampler2D u_diffuse; +uniform vec4 u_specular; +uniform float u_shininess; +void main(void) { +vec3 normal = normalize(v_normal); +vec4 color = vec4(0., 0., 0., 0.); +vec4 diffuse = vec4(0., 0., 0., 1.); +vec4 specular; +diffuse = texture2D(u_diffuse, v_texcoord0); +specular = u_specular; +diffuse.xyz *= max(dot(normal,vec3(0.,0.,1.)), 0.); +color.xyz += diffuse.xyz; +color = vec4(color.rgb * diffuse.a, diffuse.a); +gl_FragColor = color; +} diff --git a/specs/data/2.0/box-shared-image-references-separate/CesiumTexturedBoxTest0VS.glsl b/specs/data/2.0/box-shared-image-references-separate/CesiumTexturedBoxTest0VS.glsl new file mode 100644 index 00000000..cacc9ed9 --- /dev/null +++ b/specs/data/2.0/box-shared-image-references-separate/CesiumTexturedBoxTest0VS.glsl @@ -0,0 +1,15 @@ +precision highp float; +attribute vec3 a_position; +attribute vec3 a_normal; +varying vec3 v_normal; +uniform mat3 u_normalMatrix; +uniform mat4 u_modelViewMatrix; +uniform mat4 u_projectionMatrix; +attribute vec2 a_texcoord0; +varying vec2 v_texcoord0; +void main(void) { +vec4 pos = u_modelViewMatrix * vec4(a_position,1.0); +v_normal = u_normalMatrix * a_normal; +v_texcoord0 = a_texcoord0; +gl_Position = u_projectionMatrix * pos; +} diff --git a/specs/data/2.0/box-shared-image-references-separate/Image.png b/specs/data/2.0/box-shared-image-references-separate/Image.png new file mode 100644 index 00000000..d3a626a4 Binary files /dev/null and b/specs/data/2.0/box-shared-image-references-separate/Image.png differ diff --git a/specs/data/2.0/box-shared-image-references-separate/box-shared-image-references-separate.bin b/specs/data/2.0/box-shared-image-references-separate/box-shared-image-references-separate.bin new file mode 100644 index 00000000..022fbecd Binary files /dev/null and b/specs/data/2.0/box-shared-image-references-separate/box-shared-image-references-separate.bin differ diff --git a/specs/data/2.0/box-shared-image-references-separate/box-shared-image-references-separate.gltf b/specs/data/2.0/box-shared-image-references-separate/box-shared-image-references-separate.gltf new file mode 100644 index 00000000..ad14bb9d --- /dev/null +++ b/specs/data/2.0/box-shared-image-references-separate/box-shared-image-references-separate.gltf @@ -0,0 +1,262 @@ +{ + "accessors": [ + { + "bufferView": 2, + "byteOffset": 0, + "componentType": 5123, + "count": 36, + "type": "SCALAR", + "name": "accessor_indices" + }, + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5126, + "count": 24, + "max": [ + 0.5, + 0.5, + 0.5 + ], + "min": [ + -0.5, + -0.5, + -0.5 + ], + "type": "VEC3", + "name": "accessor_positions" + }, + { + "bufferView": 0, + "byteOffset": 288, + "componentType": 5126, + "count": 24, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3", + "name": "accessor_normals" + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5126, + "count": 24, + "max": [ + 6, + 1 + ], + "min": [ + 0, + 0 + ], + "type": "VEC2", + "name": "accessor_texcoord" + } + ], + "asset": { + "generator": "collada2gltf@ceec062e3d5793f2f249f53cbd843aee382ad40b", + "version": "2.0" + }, + "bufferViews": [ + { + "buffer": 0, + "byteLength": 576, + "byteOffset": 0, + "target": 34962, + "name": "bufferView_0", + "byteStride": 12 + }, + { + "buffer": 0, + "byteLength": 192, + "byteOffset": 576, + "target": 34962, + "name": "bufferView_0", + "byteStride": 8 + }, + { + "buffer": 0, + "byteLength": 72, + "byteOffset": 768, + "target": 34963, + "name": "bufferView_1" + } + ], + "buffers": [ + { + "name": "binary_glTF", + "byteLength": 840, + "uri": "box-shared-image-references-separate.bin" + } + ], + "images": [ + { + "mimeType": "image/png", + "uri": "Image.png" + }, + { + "mimeType": "image/png", + "uri": "Image.png" + } + ], + "materials": [ + { + "name": "Texture", + "extensions": { + "KHR_techniques_webgl": { + "technique": 0, + "values": { + "u_diffuse": { + "index": 0, + "texCoord": 0 + }, + "u_shininess": 256, + "u_specular": [ + 0.2, + 0.2, + 0.2, + 1 + ] + } + } + }, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "alphaMode": "OPAQUE", + "doubleSided": false + } + ], + "meshes": [ + { + "name": "Mesh", + "primitives": [ + { + "attributes": { + "NORMAL": 2, + "POSITION": 1, + "TEXCOORD_0": 3 + }, + "indices": 0, + "material": 0, + "mode": 4 + } + ] + } + ], + "nodes": [ + { + "name": "rootNode", + "mesh": 0 + } + ], + "samplers": [ + { + "magFilter": 9729, + "minFilter": 9987, + "wrapS": 10497, + "wrapT": 10497, + "name": "sampler_0" + } + ], + "scene": 0, + "scenes": [ + { + "nodes": [ + 0 + ], + "name": "defaultScene" + } + ], + "textures": [ + { + "sampler": 0, + "source": 0, + "name": "texture_Image0001" + }, + { + "sampler": 0, + "source": 1, + "name": "texture_Image0002" + } + ], + "extensionsRequired": [ + "KHR_techniques_webgl" + ], + "extensions": { + "KHR_techniques_webgl": { + "programs": [ + { + "name": "program_0", + "fragmentShader": 0, + "vertexShader": 1 + } + ], + "shaders": [ + { + "type": 35632, + "name": "CesiumTexturedBoxTest0FS", + "uri": "CesiumTexturedBoxTest0FS.glsl" + }, + { + "type": 35633, + "name": "CesiumTexturedBoxTest0VS", + "uri": "CesiumTexturedBoxTest0VS.glsl" + } + ], + "techniques": [ + { + "name": "technique0", + "program": 0, + "attributes": { + "a_normal": { + "semantic": "NORMAL" + }, + "a_position": { + "semantic": "POSITION" + }, + "a_texcoord0": { + "semantic": "TEXCOORD_0" + } + }, + "uniforms": { + "u_diffuse": { + "type": 35678 + }, + "u_modelViewMatrix": { + "type": 35676, + "semantic": "MODELVIEW" + }, + "u_normalMatrix": { + "type": 35675, + "semantic": "MODELVIEWINVERSETRANSPOSE" + }, + "u_projectionMatrix": { + "type": 35676, + "semantic": "PROJECTION" + }, + "u_shininess": { + "type": 5126 + }, + "u_specular": { + "type": 35666 + } + } + } + ] + } + }, + "extensionsUsed": [ + "KHR_techniques_webgl" + ] +} diff --git a/specs/data/2.0/box-shared-image-references/box-shared-image-references.gltf b/specs/data/2.0/box-shared-image-references/box-shared-image-references.gltf new file mode 100644 index 00000000..d42e4b2a --- /dev/null +++ b/specs/data/2.0/box-shared-image-references/box-shared-image-references.gltf @@ -0,0 +1,279 @@ +{ + "accessors": [ + { + "bufferView": 2, + "byteOffset": 0, + "componentType": 5123, + "count": 36, + "type": "SCALAR", + "name": "accessor_indices" + }, + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5126, + "count": 24, + "max": [ + 0.5, + 0.5, + 0.5 + ], + "min": [ + -0.5, + -0.5, + -0.5 + ], + "type": "VEC3", + "name": "accessor_positions" + }, + { + "bufferView": 0, + "byteOffset": 288, + "componentType": 5126, + "count": 24, + "max": [ + 1, + 1, + 1 + ], + "min": [ + -1, + -1, + -1 + ], + "type": "VEC3", + "name": "accessor_normals" + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5126, + "count": 24, + "max": [ + 6, + 1 + ], + "min": [ + 0, + 0 + ], + "type": "VEC2", + "name": "accessor_texcoord" + } + ], + "asset": { + "generator": "collada2gltf@ceec062e3d5793f2f249f53cbd843aee382ad40b", + "version": "2.0" + }, + "bufferViews": [ + { + "buffer": 0, + "byteLength": 576, + "byteOffset": 0, + "target": 34962, + "name": "bufferView_0", + "byteStride": 12 + }, + { + "buffer": 0, + "byteLength": 192, + "byteOffset": 576, + "target": 34962, + "name": "bufferView_0", + "byteStride": 8 + }, + { + "buffer": 0, + "byteLength": 72, + "byteOffset": 768, + "target": 34963, + "name": "bufferView_1" + }, + { + "buffer": 0, + "byteOffset": 840, + "byteLength": 5383 + }, + { + "buffer": 0, + "byteOffset": 6224, + "byteLength": 511 + }, + { + "buffer": 0, + "byteOffset": 6736, + "byteLength": 424 + } + ], + "buffers": [ + { + "name": "binary_glTF", + "byteLength": 7160, + "uri": "data:application/octet-stream;base64,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b3JtIHNhbXBsZXIyRCB1X2RpZmZ1c2U7CnVuaWZvcm0gdmVjNCB1X3NwZWN1bGFyOwp1bmlmb3JtIGZsb2F0IHVfc2hpbmluZXNzOwp2b2lkIG1haW4odm9pZCkgewp2ZWMzIG5vcm1hbCA9IG5vcm1hbGl6ZSh2X25vcm1hbCk7CnZlYzQgY29sb3IgPSB2ZWM0KDAuLCAwLiwgMC4sIDAuKTsKdmVjNCBkaWZmdXNlID0gdmVjNCgwLiwgMC4sIDAuLCAxLik7CnZlYzQgc3BlY3VsYXI7CmRpZmZ1c2UgPSB0ZXh0dXJlMkQodV9kaWZmdXNlLCB2X3RleGNvb3JkMCk7CnNwZWN1bGFyID0gdV9zcGVjdWxhcjsKZGlmZnVzZS54eXogKj0gbWF4KGRvdChub3JtYWwsdmVjMygwLiwwLiwxLikpLCAwLik7CmNvbG9yLnh5eiArPSBkaWZmdXNlLnh5ejsKY29sb3IgPSB2ZWM0KGNvbG9yLnJnYiAqIGRpZmZ1c2UuYSwgZGlmZnVzZS5hKTsKZ2xfRnJhZ0NvbG9yID0gY29sb3I7Cn0KAHByZWNpc2lvbiBoaWdocCBmbG9hdDsKYXR0cmlidXRlIHZlYzMgYV9wb3NpdGlvbjsKYXR0cmlidXRlIHZlYzMgYV9ub3JtYWw7CnZhcnlpbmcgdmVjMyB2X25vcm1hbDsKdW5pZm9ybSBtYXQzIHVfbm9ybWFsTWF0cml4Owp1bmlmb3JtIG1hdDQgdV9tb2RlbFZpZXdNYXRyaXg7CnVuaWZvcm0gbWF0NCB1X3Byb2plY3Rpb25NYXRyaXg7CmF0dHJpYnV0ZSB2ZWMyIGFfdGV4Y29vcmQwOwp2YXJ5aW5nIHZlYzIgdl90ZXhjb29yZDA7CnZvaWQgbWFpbih2b2lkKSB7CnZlYzQgcG9zID0gdV9tb2RlbFZpZXdNYXRyaXggKiB2ZWM0KGFfcG9zaXRpb24sMS4wKTsKdl9ub3JtYWwgPSB1X25vcm1hbE1hdHJpeCAqIGFfbm9ybWFsOwp2X3RleGNvb3JkMCA9IGFfdGV4Y29vcmQwOwpnbF9Qb3NpdGlvbiA9IHVfcHJvamVjdGlvbk1hdHJpeCAqIHBvczsKfQo=" + } + ], + "images": [ + { + "name": "Image0001", + "bufferView": 3, + "mimeType": "image/png" + }, + { + "name": "Image0002", + "bufferView": 3, + "mimeType": "image/png" + } + ], + "materials": [ + { + "name": "Texture", + "extensions": { + "KHR_techniques_webgl": { + "technique": 0, + "values": { + "u_diffuse": { + "index": 0, + "texCoord": 0 + }, + "u_shininess": 256, + "u_specular": [ + 0.2, + 0.2, + 0.2, + 1 + ] + } + } + }, + "emissiveFactor": [ + 0, + 0, + 0 + ], + "alphaMode": "OPAQUE", + "doubleSided": false + } + ], + "meshes": [ + { + "name": "Mesh", + "primitives": [ + { + "attributes": { + "NORMAL": 2, + "POSITION": 1, + "TEXCOORD_0": 3 + }, + "indices": 0, + "material": 0, + "mode": 4 + } + ] + } + ], + "nodes": [ + { + "name": "rootNode", + "mesh": 0 + } + ], + "samplers": [ + { + "magFilter": 9729, + "minFilter": 9987, + "wrapS": 10497, + "wrapT": 10497, + "name": "sampler_0" + } + ], + "scene": 0, + "scenes": [ + { + "nodes": [ + 0 + ], + "name": "defaultScene" + } + ], + "textures": [ + { + "sampler": 0, + "source": 0, + "name": "texture_Image0001" + }, + { + "sampler": 0, + "source": 1, + "name": "texture_Image0002" + } + ], + "extensionsRequired": [ + "KHR_techniques_webgl" + ], + "extensions": { + "KHR_techniques_webgl": { + "programs": [ + { + "name": "program_0", + "fragmentShader": 0, + "vertexShader": 1 + } + ], + "shaders": [ + { + "type": 35632, + "name": "CesiumTexturedBoxTest0FS", + "bufferView": 4 + }, + { + "type": 35633, + "name": "CesiumTexturedBoxTest0VS", + "bufferView": 5 + } + ], + "techniques": [ + { + "name": "technique0", + "program": 0, + "attributes": { + "a_normal": { + "semantic": "NORMAL" + }, + "a_position": { + "semantic": "POSITION" + }, + "a_texcoord0": { + "semantic": "TEXCOORD_0" + } + }, + "uniforms": { + "u_diffuse": { + "type": 35678 + }, + "u_modelViewMatrix": { + "type": 35676, + "semantic": "MODELVIEW" + }, + "u_normalMatrix": { + "type": 35675, + "semantic": "MODELVIEWINVERSETRANSPOSE" + }, + "u_projectionMatrix": { + "type": 35676, + "semantic": "PROJECTION" + }, + "u_shininess": { + "type": 5126 + }, + "u_specular": { + "type": 35666 + } + } + } + ] + } + }, + "extensionsUsed": [ + "KHR_techniques_webgl" + ] +} diff --git a/specs/lib/writeResourcesSpec.js b/specs/lib/writeResourcesSpec.js index ad0cee9b..a7426f4e 100644 --- a/specs/lib/writeResourcesSpec.js +++ b/specs/lib/writeResourcesSpec.js @@ -12,21 +12,31 @@ const CesiumMath = Cesium.Math; const gltfPath = 'specs/data/2.0/box-techniques-embedded/box-techniques-embedded.gltf'; const gltfWebpPath = 'specs/data/2.0/extensions/EXT_texture_webp/box-textured-embedded/box-textured-embedded.gltf'; const gltfWebpSeparatePath = 'specs/data/2.0/extensions/EXT_texture_webp/box-textured-separate/box-textured-with-fallback.gltf'; +const gltfSharedImageReferencesPath = 'specs/data/2.0/box-shared-image-references/box-shared-image-references.gltf'; +const gltfSharedImageReferencesSeparatePath = 'specs/data/2.0/box-shared-image-references-separate/box-shared-image-references-separate.gltf'; let gltf; let gltfWebp; let gltfWebpSeparate; +let gltfSharedImageReferences; +let gltfSharedImageReferencesSeparate; describe('writeResources', () => { beforeEach(async () => { gltf = fsExtra.readJsonSync(gltfPath); gltfWebp = fsExtra.readJsonSync(gltfWebpPath); gltfWebpSeparate = fsExtra.readJsonSync(gltfWebpSeparatePath); + gltfSharedImageReferences = fsExtra.readJsonSync(gltfSharedImageReferencesPath); + gltfSharedImageReferencesSeparate = fsExtra.readJsonSync(gltfSharedImageReferencesSeparatePath); await readResources(gltf); await readResources(gltfWebp); await readResources(gltfWebpSeparate, { resourceDirectory: path.dirname(gltfWebpSeparatePath) }); + await readResources(gltfSharedImageReferences); + await readResources(gltfSharedImageReferencesSeparate, { + resourceDirectory: path.dirname(gltfSharedImageReferencesSeparatePath) + }); }); it('writes embedded resources', () => { @@ -181,4 +191,43 @@ describe('writeResources', () => { // There should be a new bufferView for the WebP, and one for the fallback image. expect(gltfWebpSeparate.bufferViews.length).toBe(originalBufferViewsLength + 2); }); + + it('does not duplicate multiple references to the same buffer view', async () => { + const originalBufferViewsLength = gltfSharedImageReferences.bufferViews.length; + writeResources(gltfSharedImageReferences); + expect(gltfSharedImageReferences.bufferViews.length).toBe(originalBufferViewsLength); + expect(gltfSharedImageReferences.images[0].bufferView).toBe(gltfSharedImageReferences.images[1].bufferView); + }); + + it('does not duplicate multiple references to the same buffer view when saving separate resources', async () => { + const separateResources = {}; + const options = { + separateBuffers: true, + separateTextures: true, + separateShaders: true, + separateResources: separateResources + }; + writeResources(gltfSharedImageReferences, options); + expect(gltfSharedImageReferences.images[0].uri).toBeDefined(); + expect(gltfSharedImageReferences.images[0].uri).toBe(gltfSharedImageReferences.images[1].uri); + }); + + it('does not duplicate multiple references to the same uri', async () => { + writeResources(gltfSharedImageReferencesSeparate); + expect(gltfSharedImageReferencesSeparate.images[0].bufferView).toBeDefined(); + expect(gltfSharedImageReferencesSeparate.images[0].bufferView).toBe(gltfSharedImageReferencesSeparate.images[1].bufferView); + }); + + it('does not duplicate multiple references to the same uri when saving separate resources', async () => { + const separateResources = {}; + const options = { + separateBuffers: true, + separateTextures: true, + separateShaders: true, + separateResources: separateResources + }; + writeResources(gltfSharedImageReferencesSeparate, options); + expect(gltfSharedImageReferencesSeparate.images[0].uri).toBeDefined(); + expect(gltfSharedImageReferencesSeparate.images[0].uri).toBe(gltfSharedImageReferencesSeparate.images[1].uri); + }); });