multiMaterial.ts 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. import { Nullable } from "../types";
  2. import { Scene } from "../scene";
  3. import { AbstractMesh } from "../Meshes/abstractMesh";
  4. import { SubMesh } from "../Meshes/subMesh";
  5. import { BaseTexture } from "../Materials/Textures/baseTexture";
  6. import { Material } from "../Materials/material";
  7. import { Tags } from "../Misc/tags";
  8. import { _TypeStore } from '../Misc/typeStore';
  9. /**
  10. * A multi-material is used to apply different materials to different parts of the same object without the need of
  11. * separate meshes. This can be use to improve performances.
  12. * @see https://doc.babylonjs.com/how_to/multi_materials
  13. */
  14. export class MultiMaterial extends Material {
  15. private _subMaterials: Nullable<Material>[];
  16. /**
  17. * Gets or Sets the list of Materials used within the multi material.
  18. * They need to be ordered according to the submeshes order in the associated mesh
  19. */
  20. public get subMaterials(): Nullable<Material>[] {
  21. return this._subMaterials;
  22. }
  23. public set subMaterials(value: Nullable<Material>[]) {
  24. this._subMaterials = value;
  25. this._hookArray(value);
  26. }
  27. /**
  28. * Function used to align with Node.getChildren()
  29. * @returns the list of Materials used within the multi material
  30. */
  31. public getChildren(): Nullable<Material>[] {
  32. return this.subMaterials;
  33. }
  34. /**
  35. * Instantiates a new Multi Material
  36. * A multi-material is used to apply different materials to different parts of the same object without the need of
  37. * separate meshes. This can be use to improve performances.
  38. * @see https://doc.babylonjs.com/how_to/multi_materials
  39. * @param name Define the name in the scene
  40. * @param scene Define the scene the material belongs to
  41. */
  42. constructor(name: string, scene: Scene) {
  43. super(name, scene, true);
  44. scene.multiMaterials.push(this);
  45. this.subMaterials = new Array<Material>();
  46. this._storeEffectOnSubMeshes = true; // multimaterial is considered like a push material
  47. }
  48. private _hookArray(array: Nullable<Material>[]): void {
  49. var oldPush = array.push;
  50. array.push = (...items: Nullable<Material>[]) => {
  51. var result = oldPush.apply(array, items);
  52. this._markAllSubMeshesAsTexturesDirty();
  53. return result;
  54. };
  55. var oldSplice = array.splice;
  56. array.splice = (index: number, deleteCount?: number) => {
  57. var deleted = oldSplice.apply(array, [index, deleteCount]);
  58. this._markAllSubMeshesAsTexturesDirty();
  59. return deleted;
  60. };
  61. }
  62. /**
  63. * Get one of the submaterial by its index in the submaterials array
  64. * @param index The index to look the sub material at
  65. * @returns The Material if the index has been defined
  66. */
  67. public getSubMaterial(index: number): Nullable<Material> {
  68. if (index < 0 || index >= this.subMaterials.length) {
  69. return this.getScene().defaultMaterial;
  70. }
  71. return this.subMaterials[index];
  72. }
  73. /**
  74. * Get the list of active textures for the whole sub materials list.
  75. * @returns All the textures that will be used during the rendering
  76. */
  77. public getActiveTextures(): BaseTexture[] {
  78. return super.getActiveTextures().concat(...this.subMaterials.map((subMaterial) => {
  79. if (subMaterial) {
  80. return subMaterial.getActiveTextures();
  81. } else {
  82. return [];
  83. }
  84. }));
  85. }
  86. /**
  87. * Specifies if any sub-materials of this multi-material use a given texture.
  88. * @param texture Defines the texture to check against this multi-material's sub-materials.
  89. * @returns A boolean specifying if any sub-material of this multi-material uses the texture.
  90. */
  91. public hasTexture(texture: BaseTexture): boolean {
  92. if (super.hasTexture(texture)) {
  93. return true;
  94. }
  95. for (let i = 0; i < this.subMaterials.length; i++) {
  96. if (this.subMaterials[i]?.hasTexture(texture)) {
  97. return true;
  98. }
  99. }
  100. return false;
  101. }
  102. /**
  103. * Gets the current class name of the material e.g. "MultiMaterial"
  104. * Mainly use in serialization.
  105. * @returns the class name
  106. */
  107. public getClassName(): string {
  108. return "MultiMaterial";
  109. }
  110. /**
  111. * Checks if the material is ready to render the requested sub mesh
  112. * @param mesh Define the mesh the submesh belongs to
  113. * @param subMesh Define the sub mesh to look readiness for
  114. * @param useInstances Define whether or not the material is used with instances
  115. * @returns true if ready, otherwise false
  116. */
  117. public isReadyForSubMesh(mesh: AbstractMesh, subMesh: SubMesh, useInstances?: boolean): boolean {
  118. for (var index = 0; index < this.subMaterials.length; index++) {
  119. var subMaterial = this.subMaterials[index];
  120. if (subMaterial) {
  121. if (subMaterial._storeEffectOnSubMeshes) {
  122. if (!subMaterial.isReadyForSubMesh(mesh, subMesh, useInstances)) {
  123. return false;
  124. }
  125. continue;
  126. }
  127. if (!subMaterial.isReady(mesh)) {
  128. return false;
  129. }
  130. }
  131. }
  132. return true;
  133. }
  134. /**
  135. * Clones the current material and its related sub materials
  136. * @param name Define the name of the newly cloned material
  137. * @param cloneChildren Define if submaterial will be cloned or shared with the parent instance
  138. * @returns the cloned material
  139. */
  140. public clone(name: string, cloneChildren?: boolean): MultiMaterial {
  141. var newMultiMaterial = new MultiMaterial(name, this.getScene());
  142. for (var index = 0; index < this.subMaterials.length; index++) {
  143. var subMaterial: Nullable<Material> = null;
  144. let current = this.subMaterials[index];
  145. if (cloneChildren && current) {
  146. subMaterial = current.clone(name + "-" + current.name);
  147. } else {
  148. subMaterial = this.subMaterials[index];
  149. }
  150. newMultiMaterial.subMaterials.push(subMaterial);
  151. }
  152. return newMultiMaterial;
  153. }
  154. /**
  155. * Serializes the materials into a JSON representation.
  156. * @returns the JSON representation
  157. */
  158. public serialize(): any {
  159. var serializationObject: any = {};
  160. serializationObject.name = this.name;
  161. serializationObject.id = this.id;
  162. if (Tags) {
  163. serializationObject.tags = Tags.GetTags(this);
  164. }
  165. serializationObject.materials = [];
  166. for (var matIndex = 0; matIndex < this.subMaterials.length; matIndex++) {
  167. var subMat = this.subMaterials[matIndex];
  168. if (subMat) {
  169. serializationObject.materials.push(subMat.id);
  170. } else {
  171. serializationObject.materials.push(null);
  172. }
  173. }
  174. return serializationObject;
  175. }
  176. /**
  177. * Dispose the material and release its associated resources
  178. * @param forceDisposeEffect Define if we want to force disposing the associated effect (if false the shader is not released and could be reuse later on)
  179. * @param forceDisposeTextures Define if we want to force disposing the associated textures (if false, they will not be disposed and can still be use elsewhere in the app)
  180. * @param forceDisposeChildren Define if we want to force disposing the associated submaterials (if false, they will not be disposed and can still be use elsewhere in the app)
  181. */
  182. public dispose(forceDisposeEffect?: boolean, forceDisposeTextures?: boolean, forceDisposeChildren?: boolean): void {
  183. var scene = this.getScene();
  184. if (!scene) {
  185. return;
  186. }
  187. if (forceDisposeChildren) {
  188. for (var index = 0; index < this.subMaterials.length; index++) {
  189. var subMaterial = this.subMaterials[index];
  190. if (subMaterial) {
  191. subMaterial.dispose(forceDisposeEffect, forceDisposeTextures);
  192. }
  193. }
  194. }
  195. var index = scene.multiMaterials.indexOf(this);
  196. if (index >= 0) {
  197. scene.multiMaterials.splice(index, 1);
  198. }
  199. super.dispose(forceDisposeEffect, forceDisposeTextures);
  200. }
  201. /**
  202. * Creates a MultiMaterial from parsed MultiMaterial data.
  203. * @param parsedMultiMaterial defines parsed MultiMaterial data.
  204. * @param scene defines the hosting scene
  205. * @returns a new MultiMaterial
  206. */
  207. public static ParseMultiMaterial(parsedMultiMaterial: any, scene: Scene): MultiMaterial {
  208. var multiMaterial = new MultiMaterial(parsedMultiMaterial.name, scene);
  209. multiMaterial.id = parsedMultiMaterial.id;
  210. if (Tags) {
  211. Tags.AddTagsTo(multiMaterial, parsedMultiMaterial.tags);
  212. }
  213. for (var matIndex = 0; matIndex < parsedMultiMaterial.materials.length; matIndex++) {
  214. var subMatId = parsedMultiMaterial.materials[matIndex];
  215. if (subMatId) {
  216. // If the same multimaterial is loaded twice, the 2nd multimaterial needs to reference the latest material by that id which
  217. // is why this lookup should use getLastMaterialByID instead of getMaterialByID
  218. multiMaterial.subMaterials.push(scene.getLastMaterialByID(subMatId));
  219. } else {
  220. multiMaterial.subMaterials.push(null);
  221. }
  222. }
  223. return multiMaterial;
  224. }
  225. }
  226. _TypeStore.RegisteredTypes["BABYLON.MultiMaterial"] = MultiMaterial;