KHR_materials_variants.ts 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  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 } 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. /** @hidden */
  33. constructor(loader: GLTFLoader) {
  34. this._loader = loader;
  35. this.enabled = this._loader.isExtensionUsed(NAME);
  36. }
  37. /** @hidden */
  38. public dispose() {
  39. delete this._loader;
  40. }
  41. /**
  42. * Gets the list of available variant names for this asset.
  43. * @param rootMesh The glTF root mesh
  44. * @returns the list of all the variant names for this model
  45. */
  46. public static GetAvailableVariants(rootMesh: Mesh): string[] {
  47. const extensionMetadata = this._GetExtensionMetadata(rootMesh);
  48. if (!extensionMetadata) {
  49. return [];
  50. }
  51. return Object.keys(extensionMetadata.variants);
  52. }
  53. /**
  54. * Gets the list of available variant names for this asset.
  55. * @param rootMesh The glTF root mesh
  56. * @returns the list of all the variant names for this model
  57. */
  58. public getAvailableVariants(rootMesh: Mesh): string[] {
  59. return KHR_materials_variants.GetAvailableVariants(rootMesh);
  60. }
  61. /**
  62. * Select a variant given a variant name or a list of variant names.
  63. * @param rootMesh The glTF root mesh
  64. * @param variantName The variant name(s) to select.
  65. */
  66. public static SelectVariant(rootMesh: Mesh, variantName: string | string[]): void {
  67. const extensionMetadata = this._GetExtensionMetadata(rootMesh);
  68. if (!extensionMetadata) {
  69. throw new Error(`Cannot select variant on a glTF mesh that does not have the ${NAME} extension`);
  70. }
  71. const select = (variantName: string): void => {
  72. const entries = extensionMetadata.variants[variantName];
  73. if (entries) {
  74. for (const entry of entries) {
  75. entry.mesh.material = entry.material;
  76. }
  77. }
  78. };
  79. if (variantName instanceof Array) {
  80. for (const name of variantName) {
  81. select(name);
  82. }
  83. } else {
  84. select(variantName);
  85. }
  86. extensionMetadata.lastSelected = variantName;
  87. }
  88. /**
  89. * Select a variant given a variant name or a list of variant names.
  90. * @param rootMesh The glTF root mesh
  91. * @param variantName The variant name(s) to select.
  92. */
  93. public selectVariant(rootMesh: Mesh, variantName: string | string[]): void {
  94. return KHR_materials_variants.SelectVariant(rootMesh, variantName);
  95. }
  96. /**
  97. * Reset back to the original before selecting a variant.
  98. * @param rootMesh The glTF root mesh
  99. */
  100. public static Reset(rootMesh: Mesh): void {
  101. const extensionMetadata = this._GetExtensionMetadata(rootMesh);
  102. if (!extensionMetadata) {
  103. throw new Error(`Cannot reset on a glTF mesh that does not have the ${NAME} extension`);
  104. }
  105. for (const entry of extensionMetadata.original) {
  106. entry.mesh.material = entry.material;
  107. }
  108. extensionMetadata.lastSelected = null;
  109. }
  110. /**
  111. * Reset back to the original before selecting a variant.
  112. * @param rootMesh The glTF root mesh
  113. */
  114. public reset(rootMesh: Mesh): void {
  115. return KHR_materials_variants.Reset(rootMesh);
  116. }
  117. /**
  118. * Gets the last selected variant name(s) or null if original.
  119. * @param rootMesh The glTF root mesh
  120. * @returns The selected variant name(s).
  121. */
  122. public static GetLastSelectedVariant(rootMesh: Mesh): Nullable<string | string[]> {
  123. const extensionMetadata = this._GetExtensionMetadata(rootMesh);
  124. if (!extensionMetadata) {
  125. throw new Error(`Cannot get the last selected variant on a glTF mesh that does not have the ${NAME} extension`);
  126. }
  127. return extensionMetadata.lastSelected;
  128. }
  129. /**
  130. * Gets the last selected variant name(s) or null if original.
  131. * @param rootMesh The glTF root mesh
  132. * @returns The selected variant name(s).
  133. */
  134. public getLastSelectedVariant(rootMesh: Mesh): Nullable<string | string[]> {
  135. return KHR_materials_variants.GetLastSelectedVariant(rootMesh);
  136. }
  137. private static _GetExtensionMetadata(rootMesh: Mesh): Nullable<IExtensionMetadata> {
  138. return rootMesh?.metadata?.gltf?.[NAME] || null;
  139. }
  140. /** @hidden */
  141. public _loadMeshPrimitiveAsync(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Nullable<Promise<AbstractMesh>> {
  142. return GLTFLoader.LoadExtensionAsync<IKHRMaterialVariants, AbstractMesh>(context, primitive, this.name, (extensionContext, extension) => {
  143. const promises = new Array<Promise<any>>();
  144. promises.push(this._loader._loadMeshPrimitiveAsync(context, name, node, mesh, primitive, (babylonMesh) => {
  145. assign(babylonMesh);
  146. if (babylonMesh instanceof Mesh) {
  147. const babylonDrawMode = GLTFLoader._GetDrawMode(context, primitive.mode);
  148. const root = this._loader.rootBabylonMesh;
  149. const metadata = (root.metadata = root.metadata || {});
  150. const gltf = (metadata.gltf = metadata.gltf || {});
  151. const extensionMetadata: IExtensionMetadata = (gltf[NAME] = gltf[NAME] || { lastSelected: null, original: [], variants: {} });
  152. // Store the original material.
  153. extensionMetadata.original.push({ mesh: babylonMesh, material: babylonMesh.material });
  154. // For each mapping, look at the variants and make a new entry for them.
  155. const variants = extensionMetadata.variants;
  156. for (const mapping of extension.mapping) {
  157. for (const variant of mapping.variants) {
  158. const material = ArrayItem.Get(`#/materials/`, this._loader.gltf.materials, mapping.material);
  159. promises.push(this._loader._loadMaterialAsync(`#/materials/${mapping.material}`, material, babylonMesh, babylonDrawMode, (babylonMaterial) => {
  160. variants[variant] = variants[variant] || [];
  161. variants[variant].push({
  162. mesh: babylonMesh,
  163. material: babylonMaterial
  164. });
  165. }));
  166. }
  167. }
  168. }
  169. }));
  170. return Promise.all(promises).then(([babylonMesh]) => {
  171. return babylonMesh;
  172. });
  173. });
  174. }
  175. }
  176. GLTFLoader.RegisterExtension(NAME, (loader) => new KHR_materials_variants(loader));