KHR_materials_variants.ts 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. import { Nullable } from "babylonjs/types";
  2. import { IGLTFLoaderExtension } from "../glTFLoaderExtension";
  3. import { GLTFLoader, ArrayItem } from "../glTFLoader";
  4. import { Material } from 'babylonjs/Materials/material';
  5. import { Mesh } from 'babylonjs/Meshes/mesh';
  6. import { AbstractMesh } from 'babylonjs/Meshes/abstractMesh';
  7. import { INode, IMeshPrimitive, IMesh } from '../glTFLoaderInterfaces';
  8. import { IKHRMaterialVariants_Mapping, IKHRMaterialVariants_Variant, IKHRMaterialVariants_Variants } from 'babylonjs-gltf2interface';
  9. const NAME = "KHR_materials_variants";
  10. interface IVariantsMap {
  11. [key: string]: Array<{ mesh: AbstractMesh, material: Nullable<Material> }>;
  12. }
  13. interface IExtensionMetadata {
  14. lastSelected: Nullable<string | Array<string>>;
  15. original: Array<{ mesh: AbstractMesh, material: Nullable<Material> }>;
  16. variants: IVariantsMap;
  17. }
  18. /**
  19. * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1681)
  20. * !!! Experimental Extension Subject to Changes !!!
  21. */
  22. export class KHR_materials_variants implements IGLTFLoaderExtension {
  23. /**
  24. * The name of this extension.
  25. */
  26. public readonly name = NAME;
  27. /**
  28. * Defines whether this extension is enabled.
  29. */
  30. public enabled: boolean;
  31. private _loader: GLTFLoader;
  32. private _variants?: Array<IKHRMaterialVariants_Variant>;
  33. /** @hidden */
  34. constructor(loader: GLTFLoader) {
  35. this._loader = loader;
  36. this.enabled = this._loader.isExtensionUsed(NAME);
  37. }
  38. /** @hidden */
  39. public dispose() {
  40. delete this._loader;
  41. }
  42. /**
  43. * Gets the list of available variant names for this asset.
  44. * @param rootMesh The glTF root mesh
  45. * @returns the list of all the variant names for this model
  46. */
  47. public static GetAvailableVariants(rootMesh: Mesh): string[] {
  48. const extensionMetadata = this._GetExtensionMetadata(rootMesh);
  49. if (!extensionMetadata) {
  50. return [];
  51. }
  52. return Object.keys(extensionMetadata.variants);
  53. }
  54. /**
  55. * Gets the list of available variant names for this asset.
  56. * @param rootMesh The glTF root mesh
  57. * @returns the list of all the variant names for this model
  58. */
  59. public getAvailableVariants(rootMesh: Mesh): string[] {
  60. return KHR_materials_variants.GetAvailableVariants(rootMesh);
  61. }
  62. /**
  63. * Select a variant given a variant name or a list of variant names.
  64. * @param rootMesh The glTF root mesh
  65. * @param variantName The variant name(s) to select.
  66. */
  67. public static SelectVariant(rootMesh: Mesh, variantName: string | string[]): void {
  68. const extensionMetadata = this._GetExtensionMetadata(rootMesh);
  69. if (!extensionMetadata) {
  70. throw new Error(`Cannot select variant on a glTF mesh that does not have the ${NAME} extension`);
  71. }
  72. const select = (variantName: string): void => {
  73. const entries = extensionMetadata.variants[variantName];
  74. if (entries) {
  75. for (const entry of entries) {
  76. entry.mesh.material = entry.material;
  77. }
  78. }
  79. };
  80. if (variantName instanceof Array) {
  81. for (const name of variantName) {
  82. select(name);
  83. }
  84. } else {
  85. select(variantName);
  86. }
  87. extensionMetadata.lastSelected = variantName;
  88. }
  89. /**
  90. * Select a variant given a variant name or a list of variant names.
  91. * @param rootMesh The glTF root mesh
  92. * @param variantName The variant name(s) to select.
  93. */
  94. public selectVariant(rootMesh: Mesh, variantName: string | string[]): void {
  95. return KHR_materials_variants.SelectVariant(rootMesh, variantName);
  96. }
  97. /**
  98. * Reset back to the original before selecting a variant.
  99. * @param rootMesh The glTF root mesh
  100. */
  101. public static Reset(rootMesh: Mesh): void {
  102. const extensionMetadata = this._GetExtensionMetadata(rootMesh);
  103. if (!extensionMetadata) {
  104. throw new Error(`Cannot reset on a glTF mesh that does not have the ${NAME} extension`);
  105. }
  106. for (const entry of extensionMetadata.original) {
  107. entry.mesh.material = entry.material;
  108. }
  109. extensionMetadata.lastSelected = null;
  110. }
  111. /**
  112. * Reset back to the original before selecting a variant.
  113. * @param rootMesh The glTF root mesh
  114. */
  115. public reset(rootMesh: Mesh): void {
  116. return KHR_materials_variants.Reset(rootMesh);
  117. }
  118. /**
  119. * Gets the last selected variant name(s) or null if original.
  120. * @param rootMesh The glTF root mesh
  121. * @returns The selected variant name(s).
  122. */
  123. public static GetLastSelectedVariant(rootMesh: Mesh): Nullable<string | string[]> {
  124. const extensionMetadata = this._GetExtensionMetadata(rootMesh);
  125. if (!extensionMetadata) {
  126. throw new Error(`Cannot get the last selected variant on a glTF mesh that does not have the ${NAME} extension`);
  127. }
  128. return extensionMetadata.lastSelected;
  129. }
  130. /**
  131. * Gets the last selected variant name(s) or null if original.
  132. * @param rootMesh The glTF root mesh
  133. * @returns The selected variant name(s).
  134. */
  135. public getLastSelectedVariant(rootMesh: Mesh): Nullable<string | string[]> {
  136. return KHR_materials_variants.GetLastSelectedVariant(rootMesh);
  137. }
  138. private static _GetExtensionMetadata(rootMesh: Mesh): Nullable<IExtensionMetadata> {
  139. return rootMesh?.metadata?.gltf?.[NAME] || null;
  140. }
  141. /** @hidden */
  142. public onLoading(): void {
  143. const extensions = this._loader.gltf.extensions;
  144. if (extensions && extensions[this.name]) {
  145. const extension = extensions[this.name] as IKHRMaterialVariants_Variants;
  146. this._variants = extension.variants;
  147. }
  148. }
  149. /** @hidden */
  150. public _loadMeshPrimitiveAsync(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Nullable<Promise<AbstractMesh>> {
  151. return GLTFLoader.LoadExtensionAsync<IKHRMaterialVariants_Mapping, AbstractMesh>(context, primitive, this.name, (extensionContext, extension) => {
  152. const promises = new Array<Promise<any>>();
  153. promises.push(this._loader._loadMeshPrimitiveAsync(context, name, node, mesh, primitive, (babylonMesh) => {
  154. assign(babylonMesh);
  155. if (babylonMesh instanceof Mesh) {
  156. const babylonDrawMode = GLTFLoader._GetDrawMode(context, primitive.mode);
  157. const root = this._loader.rootBabylonMesh;
  158. const metadata = (root.metadata = root.metadata || {});
  159. const gltf = (metadata.gltf = metadata.gltf || {});
  160. const extensionMetadata: IExtensionMetadata = (gltf[NAME] = gltf[NAME] || { lastSelected: null, original: [], variants: {} });
  161. // Store the original material.
  162. extensionMetadata.original.push({ mesh: babylonMesh, material: babylonMesh.material });
  163. // For each mapping, look at the variants and make a new entry for them.
  164. const variants = extensionMetadata.variants;
  165. for (const mapping of extension.mapping) {
  166. for (const variantIndex of mapping.variants) {
  167. const variant = ArrayItem.Get(`${extensionContext}/mapping/${variantIndex}`, this._variants, variantIndex);
  168. const material = ArrayItem.Get(`#/materials/`, this._loader.gltf.materials, mapping.material);
  169. promises.push(this._loader._loadMaterialAsync(`#/materials/${mapping.material}`, material, babylonMesh, babylonDrawMode, (babylonMaterial) => {
  170. variants[variant.name] = variants[variant.name] || [];
  171. variants[variant.name].push({
  172. mesh: babylonMesh,
  173. material: babylonMaterial
  174. });
  175. }));
  176. }
  177. }
  178. }
  179. }));
  180. return Promise.all(promises).then(([babylonMesh]) => {
  181. return babylonMesh;
  182. });
  183. });
  184. }
  185. }
  186. GLTFLoader.RegisterExtension(NAME, (loader) => new KHR_materials_variants(loader));