KHR_materials_variants.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import { GLTFLoader, ArrayItem } from "../glTFLoader.js";
  2. import { Mesh } from "@babylonjs/core/Meshes/mesh.js";
  3. const NAME = "KHR_materials_variants";
  4. /**
  5. * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_variants/README.md)
  6. */
  7. // eslint-disable-next-line @typescript-eslint/naming-convention
  8. export class KHR_materials_variants {
  9. /**
  10. * @internal
  11. */
  12. constructor(loader) {
  13. /**
  14. * The name of this extension.
  15. */
  16. this.name = NAME;
  17. this._loader = loader;
  18. this.enabled = this._loader.isExtensionUsed(NAME);
  19. }
  20. /** @internal */
  21. dispose() {
  22. this._loader = null;
  23. }
  24. /**
  25. * Gets the list of available variant names for this asset.
  26. * @param rootMesh The glTF root mesh
  27. * @returns the list of all the variant names for this model
  28. */
  29. static GetAvailableVariants(rootMesh) {
  30. const extensionMetadata = this._GetExtensionMetadata(rootMesh);
  31. if (!extensionMetadata) {
  32. return [];
  33. }
  34. return Object.keys(extensionMetadata.variants);
  35. }
  36. /**
  37. * Gets the list of available variant names for this asset.
  38. * @param rootMesh The glTF root mesh
  39. * @returns the list of all the variant names for this model
  40. */
  41. getAvailableVariants(rootMesh) {
  42. return KHR_materials_variants.GetAvailableVariants(rootMesh);
  43. }
  44. /**
  45. * Select a variant given a variant name or a list of variant names.
  46. * @param rootMesh The glTF root mesh
  47. * @param variantName The variant name(s) to select.
  48. */
  49. static SelectVariant(rootMesh, variantName) {
  50. const extensionMetadata = this._GetExtensionMetadata(rootMesh);
  51. if (!extensionMetadata) {
  52. throw new Error(`Cannot select variant on a glTF mesh that does not have the ${NAME} extension`);
  53. }
  54. const select = (variantName) => {
  55. const entries = extensionMetadata.variants[variantName];
  56. if (entries) {
  57. for (const entry of entries) {
  58. entry.mesh.material = entry.material;
  59. }
  60. }
  61. };
  62. if (variantName instanceof Array) {
  63. for (const name of variantName) {
  64. select(name);
  65. }
  66. }
  67. else {
  68. select(variantName);
  69. }
  70. extensionMetadata.lastSelected = variantName;
  71. }
  72. /**
  73. * Select a variant given a variant name or a list of variant names.
  74. * @param rootMesh The glTF root mesh
  75. * @param variantName The variant name(s) to select.
  76. */
  77. selectVariant(rootMesh, variantName) {
  78. KHR_materials_variants.SelectVariant(rootMesh, variantName);
  79. }
  80. /**
  81. * Reset back to the original before selecting a variant.
  82. * @param rootMesh The glTF root mesh
  83. */
  84. static Reset(rootMesh) {
  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. * Reset back to the original before selecting a variant.
  96. * @param rootMesh The glTF root mesh
  97. */
  98. reset(rootMesh) {
  99. KHR_materials_variants.Reset(rootMesh);
  100. }
  101. /**
  102. * Gets the last selected variant name(s) or null if original.
  103. * @param rootMesh The glTF root mesh
  104. * @returns The selected variant name(s).
  105. */
  106. static GetLastSelectedVariant(rootMesh) {
  107. const extensionMetadata = this._GetExtensionMetadata(rootMesh);
  108. if (!extensionMetadata) {
  109. throw new Error(`Cannot get the last selected variant on a glTF mesh that does not have the ${NAME} extension`);
  110. }
  111. return extensionMetadata.lastSelected;
  112. }
  113. /**
  114. * Gets the last selected variant name(s) or null if original.
  115. * @param rootMesh The glTF root mesh
  116. * @returns The selected variant name(s).
  117. */
  118. getLastSelectedVariant(rootMesh) {
  119. return KHR_materials_variants.GetLastSelectedVariant(rootMesh);
  120. }
  121. static _GetExtensionMetadata(rootMesh) {
  122. return rootMesh?._internalMetadata?.gltf?.[NAME] || null;
  123. }
  124. /** @internal */
  125. onLoading() {
  126. const extensions = this._loader.gltf.extensions;
  127. if (extensions && extensions[this.name]) {
  128. const extension = extensions[this.name];
  129. this._variants = extension.variants;
  130. }
  131. }
  132. /**
  133. * @internal
  134. */
  135. _loadMeshPrimitiveAsync(context, name, node, mesh, primitive, assign) {
  136. return GLTFLoader.LoadExtensionAsync(context, primitive, this.name, (extensionContext, extension) => {
  137. const promises = new Array();
  138. promises.push(this._loader._loadMeshPrimitiveAsync(context, name, node, mesh, primitive, (babylonMesh) => {
  139. assign(babylonMesh);
  140. if (babylonMesh instanceof Mesh) {
  141. const babylonDrawMode = GLTFLoader._GetDrawMode(context, primitive.mode);
  142. const root = this._loader.rootBabylonMesh;
  143. const metadata = root ? (root._internalMetadata = root._internalMetadata || {}) : {};
  144. const gltf = (metadata.gltf = metadata.gltf || {});
  145. const extensionMetadata = (gltf[NAME] = gltf[NAME] || { lastSelected: null, original: [], variants: {} });
  146. // Store the original material.
  147. extensionMetadata.original.push({ mesh: babylonMesh, material: babylonMesh.material });
  148. // For each mapping, look at the variants and make a new entry for them.
  149. for (let mappingIndex = 0; mappingIndex < extension.mappings.length; ++mappingIndex) {
  150. const mapping = extension.mappings[mappingIndex];
  151. const material = ArrayItem.Get(`${extensionContext}/mappings/${mappingIndex}/material`, this._loader.gltf.materials, mapping.material);
  152. promises.push(this._loader._loadMaterialAsync(`#/materials/${mapping.material}`, material, babylonMesh, babylonDrawMode, (babylonMaterial) => {
  153. for (let mappingVariantIndex = 0; mappingVariantIndex < mapping.variants.length; ++mappingVariantIndex) {
  154. const variantIndex = mapping.variants[mappingVariantIndex];
  155. const variant = ArrayItem.Get(`/extensions/${NAME}/variants/${variantIndex}`, this._variants, variantIndex);
  156. extensionMetadata.variants[variant.name] = extensionMetadata.variants[variant.name] || [];
  157. extensionMetadata.variants[variant.name].push({
  158. mesh: babylonMesh,
  159. material: babylonMaterial,
  160. });
  161. // Replace the target when original mesh is cloned
  162. babylonMesh.onClonedObservable.add((newOne) => {
  163. const newMesh = newOne;
  164. let metadata = null;
  165. let newRoot = newMesh;
  166. // Find root to get medata
  167. do {
  168. newRoot = newRoot.parent;
  169. if (!newRoot) {
  170. return;
  171. }
  172. metadata = KHR_materials_variants._GetExtensionMetadata(newRoot);
  173. } while (metadata === null);
  174. // Need to clone the metadata on the root (first time only)
  175. if (root && metadata === KHR_materials_variants._GetExtensionMetadata(root)) {
  176. // Copy main metadata
  177. newRoot._internalMetadata = {};
  178. for (const key in root._internalMetadata) {
  179. newRoot._internalMetadata[key] = root._internalMetadata[key];
  180. }
  181. // Copy the gltf metadata
  182. newRoot._internalMetadata.gltf = [];
  183. for (const key in root._internalMetadata.gltf) {
  184. newRoot._internalMetadata.gltf[key] = root._internalMetadata.gltf[key];
  185. }
  186. // Duplicate the extension specific metadata
  187. newRoot._internalMetadata.gltf[NAME] = { lastSelected: null, original: [], variants: {} };
  188. for (const original of metadata.original) {
  189. newRoot._internalMetadata.gltf[NAME].original.push({
  190. mesh: original.mesh,
  191. material: original.material,
  192. });
  193. }
  194. for (const key in metadata.variants) {
  195. if (Object.prototype.hasOwnProperty.call(metadata.variants, key)) {
  196. newRoot._internalMetadata.gltf[NAME].variants[key] = [];
  197. for (const variantEntry of metadata.variants[key]) {
  198. newRoot._internalMetadata.gltf[NAME].variants[key].push({
  199. mesh: variantEntry.mesh,
  200. material: variantEntry.material,
  201. });
  202. }
  203. }
  204. }
  205. metadata = newRoot._internalMetadata.gltf[NAME];
  206. }
  207. // Relocate
  208. for (const target of metadata.original) {
  209. if (target.mesh === babylonMesh) {
  210. target.mesh = newMesh;
  211. }
  212. }
  213. for (const target of metadata.variants[variant.name]) {
  214. if (target.mesh === babylonMesh) {
  215. target.mesh = newMesh;
  216. }
  217. }
  218. });
  219. }
  220. }));
  221. }
  222. }
  223. }));
  224. return Promise.all(promises).then(([babylonMesh]) => {
  225. return babylonMesh;
  226. });
  227. });
  228. }
  229. }
  230. GLTFLoader.RegisterExtension(NAME, (loader) => new KHR_materials_variants(loader));
  231. //# sourceMappingURL=KHR_materials_variants.js.map