UNPKG

threepipe

Version:

A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.

135 lines 5.63 kB
/** * Materials variants extension * Modified from https://github.com/takahirox/three-gltf-extensions/blob/main/loaders/KHR_materials_variants/KHR_materials_variants.js * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_variants */ // export type OnUpdateType = ((arg0: Mesh, arg1: Material, arg2: any) => void) | null /** * KHR_materials_variants specification allows duplicated variant names * but it makes handling the extension complex. * We ensure tha names and make it easier. * If you want to export the extension with the original names * you are recommended to write GLTFExporter plugin to restore the names. * * @param variantNames {Array<string>} * @return {Array<string>} */ const ensureUniqueNames = (variantNames) => { const uniqueNames = []; const knownNames = new Set(); for (const name of variantNames) { let uniqueName = name; let suffix = 0; // @TODO: An easy solution. // O(N^2) in the worst scenario where N is variantNames.length. // Fix me if needed. while (knownNames.has(uniqueName)) { uniqueName = name + '.' + ++suffix; } knownNames.add(uniqueName); uniqueNames.push(uniqueName); } return uniqueNames; }; /** * Convert mappings array to table object to make handling the extension easier. * * @param extensionDef {glTF.meshes[n].primitive.extensions.KHR_materials_variants} * @param variantNames {Array<string>} Required to be unique names * @return {Object} */ const mappingsArrayToTable = (extensionDef, variantNames) => { const table = {}; for (const mapping of extensionDef.mappings) { for (const variant of mapping.variants) { table[variantNames[variant]] = { material: null, gltfMaterialIndex: mapping.material, }; } } return table; }; /** * @param object {THREE.Object3D} * @return {boolean} */ const compatibleObject = (object) => { return object.material !== undefined && // easier than (!object.isMesh && !object.isLine && !object.isPoints) object.userData && // just in case object.userData._variantMaterials; }; export const khrMaterialsVariantsGLTF = 'KHR_materials_variants'; export class GLTFMaterialsVariantsExtensionImport { constructor(parser) { this.parser = parser; this.name = khrMaterialsVariantsGLTF; } // Note that the following properties will be overridden even if they are pre-defined // - mesh.userData._variantMaterials async afterRoot(gltf) { const parser = this.parser; const json = parser.json; if (!json.extensions || !json.extensions[this.name]) return; const extensionDef = json.extensions[this.name]; const variantsDef = extensionDef.variants || []; const variants = ensureUniqueNames(variantsDef.map((v) => v.name)); // Save the _variantMaterials data under associated mesh.userData for (const scene of gltf.scenes) { // Save the variants data under associated mesh.userData scene.traverse(object => { const association = parser.associations.get(object); if (!association || association.meshes === undefined || association.primitives === undefined) { return; } const meshDef = json.meshes[association.meshes]; const primitiveDef = meshDef.primitives[association.primitives]; const extensionsDef = primitiveDef.extensions; if (!extensionsDef || !extensionsDef[this.name]) { return; } // object should be Mesh object.userData._variantMaterials = mappingsArrayToTable(extensionsDef[this.name], variants); }); } // gltf.userData.variants = variants /** * @param object {THREE.Mesh} * @return {Promise} */ const ensureLoadVariants = async (object) => { const currentMaterial = object.material; const variantMaterials = object.userData._variantMaterials; const pending = []; for (const variantName in variantMaterials) { const variantMaterial = variantMaterials[variantName]; if (variantMaterial.material) { continue; } const materialIndex = variantMaterial.gltfMaterialIndex; pending.push(parser.getDependency('material', materialIndex).then(material => { object.material = material; parser.assignFinalMaterial(object); variantMaterials[variantName].material = object.material; // delete variantMaterials[variantName].gltfMaterialIndex // todo; })); } return Promise.all(pending).then(() => { object.material = currentMaterial; }); }; await Promise.all(gltf.scenes.map(async (scene) => { const pending = []; scene.traverse(o => compatibleObject(o) && pending.push(ensureLoadVariants(o))); if (!scene.userData.__importData) scene.userData.__importData = {}; scene.userData.__importData[khrMaterialsVariantsGLTF] = { names: variants, }; return Promise.all(pending); })); } } //# sourceMappingURL=GLTFMaterialsVariantsExtensionImport.js.map