KHR_materials_variants.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  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 tag 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. * Select a variant given a variant tag name or a list of variant tag names.
  55. * @param rootMesh The glTF root mesh
  56. * @param variantName The variant name(s) to select.
  57. */
  58. public static SelectVariant(rootMesh: Mesh, variantName: string | string[]): void {
  59. const extensionMetadata = this._GetExtensionMetadata(rootMesh);
  60. if (!extensionMetadata) {
  61. throw new Error(`Cannot select variant on a glTF mesh that does not have the ${NAME} extension`);
  62. }
  63. const select = (variantName: string): void => {
  64. const entries = extensionMetadata.variants[variantName];
  65. if (entries) {
  66. for (const entry of entries) {
  67. entry.mesh.material = entry.material;
  68. }
  69. }
  70. };
  71. if (variantName instanceof Array) {
  72. for (const name of variantName) {
  73. select(name);
  74. }
  75. } else {
  76. select(variantName);
  77. }
  78. extensionMetadata.lastSelected = variantName;
  79. }
  80. /**
  81. * Reset back to the original before selecting a variant.
  82. * @param rootMesh The glTF root mesh
  83. */
  84. public static Reset(rootMesh: Mesh): void {
  85. const extensionMetadata = this._GetExtensionMetadata(rootMesh);
  86. if (!extensionMetadata) {
  87. throw new Error(`Cannot reset on a glTF mesh that does not have the ${NAME} extension`);
  88. }
  89. for (const entry of extensionMetadata.original) {
  90. entry.mesh.material = entry.material;
  91. }
  92. extensionMetadata.lastSelected = null;
  93. }
  94. /**
  95. * Gets the last selected variant tag name(s) or null if original.
  96. * @param rootMesh The glTF root mesh
  97. * @returns The selected variant tag name(s).
  98. */
  99. public static GetLastSelectedVariant(rootMesh: Mesh): Nullable<string | string[]> {
  100. const extensionMetadata = this._GetExtensionMetadata(rootMesh);
  101. if (!extensionMetadata) {
  102. throw new Error(`Cannot get the last selected variant on a glTF mesh that does not have the ${NAME} extension`);
  103. }
  104. return extensionMetadata.lastSelected;
  105. }
  106. private static _GetExtensionMetadata(rootMesh: Mesh): Nullable<IExtensionMetadata> {
  107. return rootMesh?.metadata?.gltf?.[NAME] || null;
  108. }
  109. /** @hidden */
  110. public _loadMeshPrimitiveAsync(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Nullable<Promise<AbstractMesh>> {
  111. return GLTFLoader.LoadExtensionAsync<IKHRMaterialVariants, AbstractMesh>(context, primitive, this.name, (extensionContext, extension) => {
  112. const promises = new Array<Promise<any>>();
  113. promises.push(this._loader._loadMeshPrimitiveAsync(context, name, node, mesh, primitive, (babylonMesh) => {
  114. assign(babylonMesh);
  115. if (babylonMesh instanceof Mesh) {
  116. const babylonDrawMode = GLTFLoader._GetDrawMode(context, primitive.mode);
  117. const root = this._loader.rootBabylonMesh;
  118. const metadata = (root.metadata = root.metadata || {});
  119. const gltf = (metadata.gltf = metadata.gltf || {});
  120. const extensionMetadata: IExtensionMetadata = (gltf[NAME] = gltf[NAME] || { lastSelected: null, original: [], variants: {} });
  121. // Store the original material.
  122. extensionMetadata.original.push({ mesh: babylonMesh, material: babylonMesh.material });
  123. // For each mapping, look at the tags and make a new entry for them.
  124. const variants = extensionMetadata.variants;
  125. for (const mapping of extension.mapping) {
  126. for (const tag of mapping.tags) {
  127. const material = ArrayItem.Get(`#/materials/`, this._loader.gltf.materials, mapping.material);
  128. promises.push(this._loader._loadMaterialAsync(`#/materials/${mapping.material}`, material, babylonMesh, babylonDrawMode, (babylonMaterial) => {
  129. variants[tag] = variants[tag] || [];
  130. variants[tag].push({
  131. mesh: babylonMesh,
  132. material: babylonMaterial
  133. });
  134. }));
  135. }
  136. }
  137. }
  138. }));
  139. return Promise.all(promises).then(([babylonMesh]) => {
  140. return babylonMesh;
  141. });
  142. });
  143. }
  144. }
  145. GLTFLoader.RegisterExtension(NAME, (loader) => new KHR_materials_variants(loader));