浏览代码

Merge pull request #8223 from MiiBond/KHR_materials_variants

Add loader for KHR_materials_variants extension
sebavan 5 年之前
父节点
当前提交
97a3a1cb02

+ 1 - 0
dist/preview release/what's new.md

@@ -86,6 +86,7 @@
 - Added support for KHR_materials_ior for glTF loader. ([Sebavan](https://github.com/sebavan/))
 - Added support for KHR_materials_specular for glTF loader. ([Sebavan](https://github.com/sebavan/))
 - Added support for KHR_xmp for glTF loader. ([Sebavan](https://github.com/sebavan/))
+- Added support for KHR_materials_variants for glTF loader. ([MiiBond](https://github.com/MiiBond/))
 
 ### Navigation
 

+ 154 - 0
loaders/src/glTF/2.0/Extensions/KHR_materials_variants.ts

@@ -0,0 +1,154 @@
+import { Nullable } from "babylonjs/types";
+import { IGLTFLoaderExtension } from "../glTFLoaderExtension";
+import { GLTFLoader, ArrayItem } from "../glTFLoader";
+
+import { Material } from 'babylonjs/Materials/material';
+import { Mesh } from 'babylonjs/Meshes/mesh';
+import { AbstractMesh } from 'babylonjs/Meshes/abstractMesh';
+import { INode, IMeshPrimitive, IMesh } from '../glTFLoaderInterfaces';
+
+const NAME = "KHR_materials_variants";
+
+interface IKHRMaterialVariantsMapping {
+    tags: string[];
+    material: number;
+}
+
+interface IKHRMaterialVariants {
+    mapping: IKHRMaterialVariantsMapping[];
+}
+
+interface IKHRMaterialVariantsTop {
+    default?: string;
+}
+
+/**
+ * Interface for the mapping from variant tag name to a mesh and material.
+ */
+interface VariantMapping {
+    mesh: AbstractMesh;
+    materialPromise: Promise<Nullable<Material>>;
+    material?: Material;
+}
+
+/**
+ * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1681)
+ * !!! Experimental Extension Subject to Changes !!!
+ */
+export class KHR_materials_variants implements IGLTFLoaderExtension {
+    /**
+     * The name of this extension.
+     */
+    public readonly name = NAME;
+
+    /**
+     * Defines whether this extension is enabled.
+     */
+    public enabled: boolean;
+
+    private _loader: GLTFLoader;
+
+    /**
+     * The default variant name.
+     */
+    public defaultVariant: string | undefined;
+
+    private _tagsToMap: { [key: string]: VariantMapping[]; } = {};
+
+    /** @hidden */
+    constructor(loader: GLTFLoader) {
+        this._loader = loader;
+        this.enabled = this._loader.isExtensionUsed(NAME);
+    }
+
+    /** @hidden */
+    public dispose() {
+        delete this._loader;
+    }
+
+    /**
+     * Return a list of available variants for this asset.
+     * @returns {string[]}
+     */
+    public getVariants(): string[] {
+        return Object.keys(this._tagsToMap);
+    }
+
+    /**
+     * Select a variant by providing a list of variant tag names.
+     *
+     * @param {(string | string[])} variantName
+     */
+    public selectVariant(variantName: string | string[]) {
+        if (variantName instanceof Array) {
+            variantName.forEach((name) => this.selectVariantTag(name));
+        } else {
+            this.selectVariantTag(variantName);
+        }
+    }
+
+    /**
+     * Select a variant by providing a single variant tag.
+     *
+     * @param {string} variantName
+     */
+    public selectVariantTag(variantName: string) {
+        // If the name is valid, switch all meshes to use materials defined by the tags
+        const variantMappings = this._tagsToMap[variantName];
+        if (variantMappings === undefined) {
+            return;
+        }
+        variantMappings.forEach((mapping: VariantMapping) => {
+            if (mapping.material) {
+                mapping.mesh.material = mapping.material;
+                return;
+            }
+            mapping.materialPromise.then((material) => {
+                mapping.mesh.material = material;
+            });
+        });
+    }
+
+    /** @hidden */
+    public onLoading(): void {
+        const extensions = this._loader.gltf.extensions;
+        if (extensions && extensions[this.name]) {
+            const extension = extensions[this.name] as IKHRMaterialVariantsTop;
+            this.defaultVariant = extension.default;
+        }
+    }
+
+    /** @hidden */
+    public _loadMeshPrimitiveAsync(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Nullable<Promise<AbstractMesh>> {
+        return GLTFLoader.LoadExtensionAsync<IKHRMaterialVariants, AbstractMesh>(context, primitive, this.name, (extensionContext, extension) => {
+            const assignMesh = (babylonMesh: AbstractMesh) => {
+                assign(babylonMesh);
+                const babylonDrawMode = (GLTFLoader as any)._GetDrawMode(context, primitive.mode);
+                // For each mapping, look at the tags and make a new entry for them
+                extension.mapping.forEach((mapping: IKHRMaterialVariantsMapping) => {
+                    mapping.tags.forEach((tag: string, index: number) => {
+                        const tagMapping = this._tagsToMap[tag] || [];
+                        const material = ArrayItem.Get(`#/materials/`, this._loader.gltf.materials, mapping.material);
+                        const meshEntry: VariantMapping = {
+                            mesh: babylonMesh,
+                            materialPromise: Promise.resolve(null)
+                        };
+                        if (babylonMesh instanceof Mesh) {
+                            meshEntry.materialPromise = this._loader._loadMaterialAsync(`#/materials/${mapping.material}`, material, babylonMesh!, babylonDrawMode, (material) => {
+                                meshEntry.material = material;
+                            });
+                        }
+                        tagMapping.push(meshEntry);
+                        this._tagsToMap[tag] = tagMapping;
+                    });
+                });
+            };
+            this._loader._disableInstancedMesh++;
+            const promise = this._loader._loadMeshPrimitiveAsync(context, name, node, mesh, primitive, assignMesh);
+            this._loader._disableInstancedMesh--;
+            return promise;
+        });
+    }
+}
+
+GLTFLoader.RegisterExtension(NAME, (loader) => new KHR_materials_variants(loader));

+ 1 - 0
loaders/src/glTF/2.0/Extensions/index.ts

@@ -8,6 +8,7 @@ export * from "./KHR_materials_clearcoat";
 export * from "./KHR_materials_sheen";
 export * from "./KHR_materials_specular";
 export * from "./KHR_materials_ior";
+export * from "./KHR_materials_variants";
 export * from "./KHR_mesh_quantization";
 export * from "./KHR_texture_basisu";
 export * from "./KHR_texture_transform";

+ 1 - 1
loaders/src/glTF/2.0/glTFLoaderExtension.ts

@@ -72,7 +72,7 @@ export interface IGLTFLoaderExtension extends IGLTFBaseLoaderExtension, IDisposa
      * @param assign A function called synchronously after parsing the glTF properties
      * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
      */
-    _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh>;
+    _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Nullable<Promise<AbstractMesh>>;
 
     /**
      * @hidden

+ 1 - 1
package.json

@@ -110,4 +110,4 @@
         "xhr2": "^0.1.4",
         "xmlbuilder": "8.2.2"
     }
-}
+}