From c05384d3043e386c9fe31b581ab51d01d5375aef Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Wed, 7 Sep 2022 21:52:23 -0400 Subject: [PATCH] [cli] Handle conflicting URIs during merge() --- packages/cli/src/transforms/merge.ts | 59 ++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/transforms/merge.ts b/packages/cli/src/transforms/merge.ts index 77f3ff0da..2cb245a44 100644 --- a/packages/cli/src/transforms/merge.ts +++ b/packages/cli/src/transforms/merge.ts @@ -1,6 +1,15 @@ import fs from 'fs'; -import { Document, FileUtils, ImageUtils, NodeIO, Transform } from '@gltf-transform/core'; -import { unpartition } from '@gltf-transform/functions'; +import { + Document, + FileUtils, + ImageUtils, + NodeIO, + Transform, + Texture, + Buffer, + PropertyType, +} from '@gltf-transform/core'; +import { dedup, unpartition } from '@gltf-transform/functions'; const NAME = 'merge'; @@ -13,8 +22,8 @@ export interface MergeOptions { const merge = (options: MergeOptions): Transform => { const { paths, io } = options; - return async (doc: Document): Promise => { - const logger = doc.getLogger(); + return async (document: Document): Promise => { + const logger = document.getLogger(); for (let i = 0; i < paths.length; i++) { const path = paths[i]; @@ -24,21 +33,28 @@ const merge = (options: MergeOptions): Transform => { const basename = FileUtils.basename(path); const extension = FileUtils.extension(path).toLowerCase(); if (['png', 'jpg', 'jpeg', 'webp', 'ktx2'].includes(extension)) { - doc.createTexture(basename) + document + .createTexture(basename) .setImage(fs.readFileSync(path)) .setMimeType(ImageUtils.extensionToMimeType(extension)) .setURI(basename + '.' + extension); } else if (['gltf', 'glb'].includes(extension)) { - doc.merge(renameScenes(basename, await io.read(path))); + document.merge(renameScenes(basename, await io.read(path))); } else { throw new Error(`Unknown file extension: "${extension}".`); } } - doc.getRoot().setDefaultScene(doc.getRoot().listScenes()[0]); + document.getRoot().setDefaultScene(document.getRoot().listScenes()[0]); + + // De-duplicate textures, then ensure that all remaining textures and buffers + // have unique URIs. See https://github.com/donmccurdy/glTF-Transform/issues/586. + await document.transform(dedup({ propertyTypes: [PropertyType.TEXTURE] })); + createUniqueURIs(document.getRoot().listBuffers()); + createUniqueURIs(document.getRoot().listTextures()); if (!options.partition) { - await doc.transform(unpartition()); + await document.transform(unpartition()); } logger.debug(`${NAME}: Complete.`); @@ -57,4 +73,31 @@ function renameScenes(name: string, document: Document): Document { return document; } +/** Replaces conflicting URIs to ensure all URIs are unique. */ +function createUniqueURIs(resources: Buffer[] | Texture[]): void { + const total = {} as Record; + const used = {} as Record; + + for (const resource of resources) { + const uri = resource.getURI(); + if (!uri) continue; + if (!total[uri]) total[uri] = 0; + total[uri]++; + used[uri] = false; + } + + for (const resource of resources) { + let uri = resource.getURI(); + if (!uri || total[uri] === 1) continue; + + const extension = FileUtils.extension(uri); + const prefix = uri.replace(new RegExp(`\\.${extension}`), ''); + for (let i = 2; used[uri]; i++) { + uri = `${prefix}_${i++}.${extension}`; + } + resource.setURI(uri); + used[uri] = true; + } +} + export { merge };