module BABYLON { /** * A multi-material is used to apply different materials to different parts of the same object without the need of * separate meshes. This can be use to improve performances. * @see http://doc.babylonjs.com/how_to/multi_materials */ export class MultiMaterial extends Material { private _subMaterials: Nullable[]; /** * Gets or Sets the list of Materials used within the multi material. * They need to be ordered according to the submeshes order in the associated mesh */ public get subMaterials(): Nullable[] { return this._subMaterials; } public set subMaterials(value: Nullable[]) { this._subMaterials = value; this._hookArray(value); } /** * Instantiates a new Multi Material * A multi-material is used to apply different materials to different parts of the same object without the need of * separate meshes. This can be use to improve performances. * @see http://doc.babylonjs.com/how_to/multi_materials * @param name Define the name in the scene * @param scene Define the scene the material belongs to */ constructor(name: string, scene: Scene) { super(name, scene, true); scene.multiMaterials.push(this); this.subMaterials = new Array(); this._storeEffectOnSubMeshes = true; // multimaterial is considered like a push material } private _hookArray(array: Nullable[]): void { var oldPush = array.push; array.push = (...items: Nullable[]) => { var result = oldPush.apply(array, items); this._markAllSubMeshesAsTexturesDirty(); return result; }; var oldSplice = array.splice; array.splice = (index: number, deleteCount?: number) => { var deleted = oldSplice.apply(array, [index, deleteCount]); this._markAllSubMeshesAsTexturesDirty(); return deleted; }; } /** * Get one of the submaterial by its index in the submaterials array * @param index The index to look the sub material at * @returns The Material if the index has been defined */ public getSubMaterial(index: number): Nullable { if (index < 0 || index >= this.subMaterials.length) { return this.getScene().defaultMaterial; } return this.subMaterials[index]; } /** * Get the list of active textures for the whole sub materials list. * @returns All the textures that will be used during the rendering */ public getActiveTextures(): BaseTexture[] { return super.getActiveTextures().concat(...this.subMaterials.map((subMaterial) => { if (subMaterial) { return subMaterial.getActiveTextures(); } else { return []; } })); } /** * Gets the current class name of the material e.g. "MultiMaterial" * Mainly use in serialization. * @returns the class name */ public getClassName(): string { return "MultiMaterial"; } /** * Checks if the material is ready to render the requested sub mesh * @param mesh Define the mesh the submesh belongs to * @param subMesh Define the sub mesh to look readyness for * @param useInstances Define whether or not the material is used with instances * @returns true if ready, otherwise false */ public isReadyForSubMesh(mesh: AbstractMesh, subMesh: BaseSubMesh, useInstances?: boolean): boolean { for (var index = 0; index < this.subMaterials.length; index++) { var subMaterial = this.subMaterials[index]; if (subMaterial) { if (subMaterial._storeEffectOnSubMeshes) { if (!subMaterial.isReadyForSubMesh(mesh, subMesh, useInstances)) { return false; } continue; } if (!subMaterial.isReady(mesh)) { return false; } } } return true; } /** * Clones the current material and its related sub materials * @param name Define the name of the newly cloned material * @param cloneChildren Define if submaterial will be cloned or shared with the parent instance * @returns the cloned material */ public clone(name: string, cloneChildren?: boolean): MultiMaterial { var newMultiMaterial = new MultiMaterial(name, this.getScene()); for (var index = 0; index < this.subMaterials.length; index++) { var subMaterial: Nullable = null; let current = this.subMaterials[index]; if (cloneChildren && current) { subMaterial = current.clone(name + "-" + current.name); } else { subMaterial = this.subMaterials[index]; } newMultiMaterial.subMaterials.push(subMaterial); } return newMultiMaterial; } /** * Serializes the materials into a JSON representation. * @returns the JSON representation */ public serialize(): any { var serializationObject: any = {}; serializationObject.name = this.name; serializationObject.id = this.id; if (Tags) { serializationObject.tags = Tags.GetTags(this); } serializationObject.materials = []; for (var matIndex = 0; matIndex < this.subMaterials.length; matIndex++) { var subMat = this.subMaterials[matIndex]; if (subMat) { serializationObject.materials.push(subMat.id); } else { serializationObject.materials.push(null); } } return serializationObject; } /** * Dispose the material and release its associated resources * @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) * @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) */ public dispose(forceDisposeEffect?: boolean, forceDisposeTextures?: boolean): void { var scene = this.getScene(); if (!scene) { return; } var index = scene.multiMaterials.indexOf(this); if (index >= 0) { scene.multiMaterials.splice(index, 1); } super.dispose(forceDisposeEffect, forceDisposeTextures); } } }