bool CreateMultiMaterialDataButton(bool changed)
        {
            EditorGUI.BeginDisabledGroup(m_Renderer == null);
            if (GUILayout.Button("Create From Renderer"))
            {
                var saveMultiMaterialData = CreateInstance <MultiMaterialData>();
                var rendererMaterialArray = new MaterialArray {
                    materials = m_Renderer.sharedMaterials
                };
                saveMultiMaterialData.materialArrayData = rendererMaterialArray;

                var path = EditorUtility.SaveFilePanelInProject("Multi Material Data Save Window",
                                                                m_Renderer.gameObject.name + " Multi Material Data", "asset",
                                                                "Enter a file name to save the multi material data to");

                if (!string.IsNullOrEmpty(path))
                {
                    AssetDatabase.CreateAsset(saveMultiMaterialData, path);
                    AssetDatabase.Refresh();
                }

                var loadMultiMaterialData = AssetDatabase.LoadAssetAtPath <MultiMaterialData>(path);
                if (loadMultiMaterialData != null)
                {
                    serializedObject.Update();
                    var dataProp = serializedObject.FindProperty(MultiMaterial.multiMaterialDataPub);
                    dataProp.objectReferenceValue = loadMultiMaterialData;
                    serializedObject.ApplyModifiedProperties();
                }
                ValidateEditorData();
                return(true);
            }
            EditorGUI.EndDisabledGroup();
            return(changed);
        }
        public static void SetCheckMaterialShaders(MaterialArray materialArray, Material mat)
        {
            var matSerial = new SerializedObject(mat);

            matSerial.Update();
            var shaderSerial = matSerial.FindProperty("m_Shader");

            matSerial.ApplyModifiedProperties();

            foreach (var material in materialArray.materials)
            {
                if (material == null || material.name == k_DefaultMaterial)
                {
                    continue;
                }
                var targetMatSerial = new SerializedObject(material);
                var targetShader    = targetMatSerial.FindProperty("m_Shader");
                if (shaderSerial != targetShader)
                {
                    targetMatSerial.Update();
                    targetShader.objectReferenceValue = shaderSerial.objectReferenceValue;
                    targetMatSerial.ApplyModifiedProperties();
                }
            }
        }
        /// <summary>
        /// Checks that the individual Material Editor's material in the 'materialEditors' array matches the material
        /// at the corresponding index in the 'materialArray'
        /// </summary>
        /// <param name="materialEditors">Material Editor Array that will be checked against 'materialArray'</param>
        /// <param name="materialArray">Material Array that the 'materialEditors' should match</param>
        /// <returns></returns>
        public static bool CheckMaterialEditors(MaterialEditor[] materialEditors, MaterialArray materialArray)
        {
            if (materialEditors == null || materialEditors.Length < 1)
            {
                return(false);
            }

            if (materialArray == null || materialArray.materials.Length < 1)
            {
                return(false);
            }

            if (materialEditors.Length != materialArray.materials.Length)
            {
                return(false);
            }

            for (var i = 0; i < materialArray.materials.Length; i++)
            {
                if (materialArray.materials[i] == null && materialEditors[i] == null)
                {
                    continue;
                }

                if (materialArray.materials[i] != null && materialEditors[i] == null ||
                    materialEditors[i] != null && materialArray.materials[i] == null ||
                    (Material)materialEditors[i].target != materialArray.materials[i])
                {
                    return(false);
                }
            }
            return(true);
        }
        public static bool AddSelectedButton(SerializedObject serializedObject, MaterialArray materialArray,
                                             string text, bool includeChildren, bool includeInactive)
        {
            if (GUILayout.Button(text))
            {
                serializedObject.Update();
                var matHash = new HashSet <Material>();
                if (materialArray.materials.Length > 0)
                {
                    foreach (var mat in materialArray.materials)
                    {
                        matHash.Add(mat);
                    }
                }
                foreach (var obj in Selection.objects)
                {
                    var mat = obj as Material;
                    if (mat != null)
                    {
                        matHash.Add(mat);
                    }
                    var go = obj as GameObject;
                    if (go != null)
                    {
                        var meshRenderers = includeChildren? go.GetComponentsInChildren <MeshRenderer>(includeInactive)
                            : go.GetComponents <MeshRenderer>();
                        foreach (var meshRenderer in meshRenderers)
                        {
                            foreach (var sharedMaterial in meshRenderer.sharedMaterials)
                            {
                                matHash.Add(sharedMaterial);
                            }
                        }
                        var skinnedMeshRenderers = includeChildren?
                                                   go.GetComponentsInChildren <SkinnedMeshRenderer>(includeInactive)
                            : go.GetComponents <SkinnedMeshRenderer>();
                        foreach (var skinnedMeshRenderer in skinnedMeshRenderers)
                        {
                            foreach (var sharedMaterial in skinnedMeshRenderer.sharedMaterials)
                            {
                                matHash.Add(sharedMaterial);
                            }
                        }
                    }
                }
                Undo.RecordObject(serializedObject.targetObject, "add selected");
                materialArray.materials = matHash.ToArray();

                serializedObject.ApplyModifiedProperties();
                return(true);
            }
            return(false);
        }
        public static bool AddSelectedButtons(SerializedObject serializedObject, MaterialArray materialArray)
        {
            EditorGUILayout.BeginVertical();
            var changed = AddSelectedButton(serializedObject, materialArray, "Add Selected", false, false);

            EditorGUILayout.BeginHorizontal();
            changed = AddSelectedButton(serializedObject, materialArray, "Include Children", true, false) || changed;
            changed = AddSelectedButton(serializedObject, materialArray, "Include Inactive", true, true) || changed;
            EditorGUILayout.EndHorizontal();
            EditorGUILayout.EndVertical();
            return(changed);
        }
        void ValidateEditorData()
        {
            if (m_MultiMaterial.multiMaterialData != null && m_MultiMaterialData == null)
            {
                m_MultiMaterialData = new SerializedObject(m_MultiMaterial.multiMaterialData);
            }

            if (useRenderer)
            {
                if (m_Renderer == null)
                {
                    m_Renderer = m_MultiMaterial.GetComponent <Renderer>();
                }

                m_RendererMaterialArray = m_Renderer != null ?
                                          new MaterialArray {
                    materials = m_Renderer.sharedMaterials
                } : null;
                m_MaterialProperties = null;
            }
            else
            {
                m_Renderer = null;
                m_RendererMaterialArray = null;
            }

            if (m_Renderer == null)
            {
                m_Materials = m_MultiMaterialData.FindProperty(string.Format("{0}.{1}",
                                                                             MultiMaterialData.materialArrayPub, MaterialArray.materialsPub));

                var materialPropList = new List <SerializedProperty>();
                for (var i = 0; i < m_Materials.arraySize; ++i)
                {
                    materialPropList.Add(m_Materials.GetArrayElementAtIndex(i));
                }
                m_MaterialProperties = materialPropList.ToArray();
            }
        }
        /// <summary>
        /// Popup Shader selector that mimics the material shader popup in the material header
        /// </summary>
        /// <param name="material">The material that is the parent of the shader for popup.</param>
        /// <param name="materialArray">Material array the shader popup operates on.</param>
        public static void ShaderPopup(Material material, MaterialArray materialArray)
        {
            var index = Array.FindIndex(shaderNames, s => s == material.shader.name);

            // Have to use our own popup since you cannot use the material editor popup
            if (index < 0 || index > shaderNames.Length)
            {
                UpdateShaderNames(material);
                EditorGUILayout.Popup(index, shaderNameGUIContents);
                return;
            }
            index = EditorGUILayout.Popup(index, shaderNameGUIContents);
            if (shaderNames[index] != material.shader.name)
            {
                var matSerial = new SerializedObject(material);
                matSerial.Update();

                var shaderSerial = matSerial.FindProperty("m_Shader");
                shaderSerial.objectReferenceValue = Shader.Find(shaderNames[index]);
                matSerial.ApplyModifiedProperties();

                MultiMaterialEditorUtilities.SetCheckMaterialShaders(materialArray, material);
            }
        }
        public static void UpdateMaterials(MaterialArray materialArray,
                                           Material controlMaterial, bool syncAll = false)
        {
            if (materialArray.materials.Length < 1 && controlMaterial == null)
            {
                return;
            }

            SetCheckMaterialShaders(materialArray, controlMaterial);
            var controlMaterialObject = new SerializedObject(controlMaterial);
            SerializedObject checkedMaterialObject = null;

            if (!syncAll)
            {
                Material checkedMaterial = null;
                foreach (var material in materialArray.materials)
                {
                    if (material != controlMaterial && material.name != k_DefaultMaterial)
                    {
                        checkedMaterial = material;
                        break;
                    }
                }
                if (checkedMaterial == null)
                {
                    return;
                }
                checkedMaterialObject = new SerializedObject(checkedMaterial);
            }

            // Find the Property (Properties) that changed in the Material Array
            var propertiesToChange = GetPropertiesToChange(controlMaterialObject, checkedMaterialObject, syncAll);

            var matHash = new HashSet <Material>(materialArray.materials);

            //Convert each material in the hash into a list of serialized accessors.
            var matObjs = (from mat in matHash
                           where mat != null && mat.name != k_DefaultMaterial
                           select new SerializedObject(mat)).ToList();

            if (propertiesToChange != null && propertiesToChange.Count > 0)
            {
                foreach (var matObj in matObjs)
                {
                    matObj.Update();
                }

                foreach (var propertyToChange in propertiesToChange)
                {
                    foreach (var matObj in matObjs)
                    {
                        var serializedProperty = matObj.FindProperty(propertyToChange.Key);
                        if (serializedProperty != null)
                        {
                            switch (serializedProperty.propertyType)
                            {
                            case SerializedPropertyType.AnimationCurve:
                                serializedProperty.animationCurveValue =
                                    propertyToChange.Value.animationCurveValue;
                                break;

                            case SerializedPropertyType.ArraySize:
                                if (serializedProperty.isArray)
                                {
                                    serializedProperty.arraySize = propertyToChange.Value.arraySize;
                                }
                                break;

                            case SerializedPropertyType.Boolean:
                                serializedProperty.boolValue = propertyToChange.Value.boolValue;
                                break;

                            case SerializedPropertyType.Bounds:
                                serializedProperty.boundsValue = propertyToChange.Value.boundsValue;
                                break;

                            case SerializedPropertyType.Character:
                                break;

                            case SerializedPropertyType.Color:
                                serializedProperty.colorValue = propertyToChange.Value.colorValue;
                                break;

                            case SerializedPropertyType.Enum:
                                serializedProperty.enumValueIndex = propertyToChange.Value.enumValueIndex;
                                break;

#if UNITY_2017_1_OR_NEWER
                            case SerializedPropertyType.ExposedReference:
                                serializedProperty.exposedReferenceValue =
                                    propertyToChange.Value.exposedReferenceValue;
                                break;

                            case SerializedPropertyType.FixedBufferSize:
                                // SerializedProperty.fixedBufferSize is read only
                                break;
#endif
                            case SerializedPropertyType.Generic:
                                break;

                            case SerializedPropertyType.Gradient:
                                break;

                            case SerializedPropertyType.Float:
                                serializedProperty.floatValue = propertyToChange.Value.floatValue;
                                break;

                            case SerializedPropertyType.Integer:
                                serializedProperty.intValue = propertyToChange.Value.intValue;
                                break;

                            case SerializedPropertyType.String:
                                serializedProperty.stringValue = propertyToChange.Value.stringValue;
                                break;

                            case SerializedPropertyType.Rect:
                                serializedProperty.rectValue = propertyToChange.Value.rectValue;
                                break;

                            case SerializedPropertyType.Quaternion:
                                serializedProperty.quaternionValue = propertyToChange.Value.quaternionValue;
                                break;

                            case SerializedPropertyType.Vector2:
                                serializedProperty.vector2Value = propertyToChange.Value.vector2Value;
                                break;

                            case SerializedPropertyType.Vector3:
                                serializedProperty.vector3Value = propertyToChange.Value.vector3Value;
                                break;

                            case SerializedPropertyType.Vector4:
                                serializedProperty.vector4Value = propertyToChange.Value.vector4Value;
                                break;

                            case SerializedPropertyType.ObjectReference:
                                serializedProperty.objectReferenceValue =
                                    propertyToChange.Value.objectReferenceValue;
                                break;

                            case SerializedPropertyType.LayerMask:
                                break;
                            }
                        }
                    }
                }
                foreach (var matObj in matObjs)
                {
                    matObj.ApplyModifiedProperties();
                }
            }
        }
        /// <summary>
        /// Draw the Multi Material Inspector GUI using Material Editors for each material in Material Array
        /// </summary>
        /// <param name="serializedObject">Target serialized object from the inspector</param>
        /// <param name="materialArray">Material array to be used in inspector</param>
        /// <param name="materialEditors">Material Editors for each material in materialArray</param>
        /// <param name="changed">Editor property changed from outside of this method</param>
        /// <param name="materialProperties">Array of serialized properties that are the materials in the Material
        /// Array. Used for property drawer in material header</param>
        public static void OnInspectorGUI(SerializedObject serializedObject, MaterialArray materialArray,
                                          ref MaterialEditor[] materialEditors, bool changed = false, SerializedProperty[] materialProperties = null)
        {
            var materialEditorReady = true;

            if (changed || !CheckMaterialEditors(materialEditors, materialArray))
            {
                materialEditorReady = RebuildMaterialEditors(ref materialEditors, materialArray) &&
                                      Event.current.type == EventType.Layout;
            }

            if (materialEditorReady)
            {
                for (var i = 0; i < materialEditors.Length; i++)
                {
                    if (materialArray.materials[i] != null && materialEditors[i] != null)
                    {
                        var material = materialEditors[i].target as Material;

                        OnMiniMaterialArrayHeaderGUI(serializedObject, ref materialEditors[i], materialArray,
                                                     materialProperties != null && materialProperties.Length > i &&
                                                     materialProperties[i] != null? materialProperties[i] : null);

                        EditorGUI.BeginDisabledGroup(material != null && material.name == k_DefaultMaterial);
                        // Draw the Material Editor Body
                        if (materialEditors[i].isVisible)
                        {
                            EditorGUI.BeginChangeCheck();
                            if (GUILayout.Button("Sync to Material"))
                            {
                                MultiMaterialEditorUtilities.UpdateMaterials(materialArray, material, true);
                            }
                            materialEditors[i].OnInspectorGUI();

                            if (EditorGUI.EndChangeCheck())
                            {
                                MultiMaterialEditorUtilities.UpdateMaterials(materialArray, material);
                            }
                        }
                        EditorGUI.EndDisabledGroup();
                    }
                    else
                    {
                        if (materialProperties != null)
                        {
                            EditorGUI.BeginChangeCheck();
                            materialProperties[i].serializedObject.Update();
                            EditorGUILayout.PropertyField(materialProperties[i], new GUIContent("Material"));
                            materialProperties[i].serializedObject.ApplyModifiedProperties();
                            if (EditorGUI.EndChangeCheck())
                            {
                                RebuildMaterialEditors(ref materialEditors, materialArray);
                            }
                        }
                        else
                        {
                            EditorGUILayout.LabelField(k_NullMaterialWarning, s_RichTextStyle);
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Rebuilds material editor if materials in editor do not match those in the material array
        /// </summary>
        /// <param name="materialEditors">Material Editors for each material in materialArray</param>
        /// <param name="materialArray">Material array to be used in inspector</param>
        /// <returns></returns>
        public static bool RebuildMaterialEditors(ref MaterialEditor[] materialEditors, MaterialArray materialArray)
        {
            // If check fails try to rebuild editors then recheck
            if (!CheckMaterialEditors(materialEditors, materialArray))
            {
                if (materialEditors != null)
                {
                    for (var i = 0; i < materialEditors.Length; i++)
                    {
                        if (materialEditors[i] == null)
                        {
                            continue;
                        }

                        UnityObject.DestroyImmediate(materialEditors[i]);
                        materialEditors[i] = null;
                    }
                }

                var rebuildShaders = true;

                if (materialArray != null && materialArray.materials != null && materialArray.materials.Length > 0)
                {
                    materialEditors = new MaterialEditor[materialArray.materials.Length];
                    for (var i = 0; i < materialArray.materials.Length; i++)
                    {
                        var material = materialArray.materials[i];
                        if (material == null)
                        {
                            continue;
                        }

                        materialEditors[i] = Editor.CreateEditor(material) as MaterialEditor;
                        if (!rebuildShaders)
                        {
                            continue;
                        }

                        UpdateShaderNames(material);
                        rebuildShaders = false;
                    }
                }
                else
                {
                    materialEditors = new MaterialEditor[0];
                }
                // Need to try and recheck after rebuild to avoid change in gui between layout and repaint
                return(CheckMaterialEditors(materialEditors, materialArray));
            }
            return(true);
        }
        /// <summary>
        /// Draws a custom gui that mimics the Material Editor Header.
        /// We need to use custom gui since the normal Material Header does not respect all the editor gui functions
        /// that can surround it and since we cannot detect changes in the Shader Foldout
        /// </summary>
        /// <param name="serializedObject">Target serialized object from the inspector</param>
        /// <param name="materialEditor">Material Editors for each material in materialArray</param>
        /// <param name="materialArray">Material array to be used in inspector</param>
        /// <param name="serializedMaterial">Serialized property that represents the material field in the header
        /// If null the material name is drawn in place of the field.</param>
        public static void OnMiniMaterialArrayHeaderGUI(SerializedObject serializedObject,
                                                        ref MaterialEditor materialEditor, MaterialArray materialArray,
                                                        SerializedProperty serializedMaterial = null)
        {
            if (materialEditor == null || !(materialEditor.target is Material))
            {
                return;
            }

            EditorGUILayout.BeginHorizontal(EditorStyles.textArea); // Begin Header Area
            if (s_Foldout == null || s_FoldoutPreDrop == null)
            {
                FindMaterialArrayIcons();
            }

            // Material Editor body is only drawn when 'm_IsVisible' == true
            // this is normally set in the Material Editor Inspector's Header with the foldout
            // We need to be able to read and write to private field to see the material editor body.
            var isVisibleField = typeof(MaterialEditor).GetField("m_IsVisible",
                                                                 BindingFlags.NonPublic | BindingFlags.Instance);
            var isVisibleValue = isVisibleField.GetValue(materialEditor) as bool? ?? false;

            if (GUILayout.Button(isVisibleValue? s_Foldout: s_FoldoutPreDrop, GUIStyle.none,
                                 GUILayout.ExpandWidth(false)))
            {
                isVisibleField.SetValue(materialEditor, !isVisibleValue);
            }

            var material = (Material)materialEditor.target;
            var iconRect = EditorGUILayout.GetControlRect(false, k_IconSize, GUILayout.MaxWidth(k_IconSize));

            OnHeaderIconGUI(ref materialEditor, iconRect);

            EditorGUILayout.BeginVertical(); // Begin Title and Shader Area
            if (serializedMaterial == null)
            {
                EditorGUILayout.LabelField(new GUIContent(material.name));
            }
            else
            {
                serializedMaterial.serializedObject.Update();
                EditorGUILayout.PropertyField(serializedMaterial, GUIContent.none);
                serializedMaterial.serializedObject.ApplyModifiedProperties();
            }

            EditorGUI.BeginDisabledGroup(material != null && material.name == k_DefaultMaterial);
            ShaderPopup(material, materialArray);
            EditorGUI.EndDisabledGroup();

            EditorGUILayout.EndVertical();   // End Title and Shader Area
            EditorGUILayout.EndHorizontal(); // End Header Area
        }