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
JavaScript
/**
* 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