using System; using System.ComponentModel; using System.Reflection; using System.Text; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; using static UnityEditor.Rendering.Universal.MaterialReferenceBuilder; using Object = UnityEngine.Object; namespace UnityEditor.Rendering.Universal { internal class MaterialReferenceChanger : IDisposable { MaterialReferenceBuilder m_Builder; public MaterialReferenceChanger() { m_Builder = new MaterialReferenceBuilder(); } public void Dispose() { m_Builder?.Dispose(); m_Builder = null; } internal static bool AreMaterialsEqual(Material a, Material b) { return a == b; } internal static bool AreMaterialsEqual(Material[] a, Material[] b) { if (a == null || b == null) return a == b; // both must be null to be equal if (a.Length != b.Length) return false; for (int i = 0; i < a.Length; i++) { if (!AreMaterialsEqual(a[i], b[i])) return false; } return true; } private static bool TryChangeMaterialArray(Func getter, Action setter) { var materials = getter() as Material[]; if (materials == null) return true; bool setIsNeeded = false; for (int i = 0; i < materials.Length; ++i) { if (ReadonlyMaterialMap.TryGetMappingMaterial(materials[i], out var mappingMaterial)) { materials[i] = mappingMaterial; setIsNeeded = true; } } if (setIsNeeded) { setter(materials); return AreMaterialsEqual(getter() as Material[], materials); } return true; } private static bool TryChangeMaterial(Func getter, Action setter) { var material = getter() as Material; if (ReadonlyMaterialMap.TryGetMappingMaterial(material, out var mappingMaterial)) { setter(mappingMaterial); var updated = getter() as Material; return updated == mappingMaterial; } return true; } private static bool ReassignMaterialsFromInstance(object obj, MemberInfo member, bool isArray) { if (obj == null || member == null) return false; if (!TryGetFromMemberInfoAccessors(obj, member, out var getter, out var setter)) return false; return (isArray) ? TryChangeMaterialArray(getter, setter) : TryChangeMaterial(getter, setter); } internal bool ReassignGameObjectMaterials(GameObject go, StringBuilder errors) { bool ok = true; foreach (var entry in m_Builder.GetMaterialReferenceLookUps()) { var components = go.GetComponentsInChildren(entry.type); foreach (var component in components) { try { UnityEngine.Component prefabSource = null; if (PrefabUtility.IsPartOfPrefabInstance(component)) prefabSource = PrefabUtility.GetCorrespondingObjectFromSource(component); ok &= ReassignMaterials(component, prefabSource, entry, errors); } catch (Exception ex) { errors.Append($"{ex.Message} while trying to reassign materials from {component}."); } } } return ok; } private bool ReassignMaterialsFromInstaceIfOverriden(object obj, object prefabObj, MemberInfo member, bool isArray, StringBuilder errors) { if (!TryGetFromMemberInfoAccessors(obj, member, out var getter, out var setter)) { errors.AppendLine($"Unable to retrieve material accessors from {obj}"); return false; } if (!TryGetFromMemberInfoAccessors(prefabObj, member, out var getterPrefab, out var setterPrefab)) { errors.AppendLine($"Unable to retrieve material accessors from {obj}"); return false; } if (isArray) { Material[] instanceMaterials = getter() as Material[]; Material[] prefabMaterials = getterPrefab() as Material[]; if (AreMaterialsEqual(instanceMaterials, prefabMaterials)) return true; // They are the same, nothing to do, as the materials must be changed from the prefab return TryChangeMaterialArray(getter, setter); } Material instanceMaterial = getter() as Material; Material prefabMaterial = getterPrefab() as Material; if (AreMaterialsEqual(instanceMaterial, prefabMaterial)) return true; // They are the same, nothing to do, as the materials must be changed from the prefab return TryChangeMaterial(getter, setter); } public bool ReassignMaterials(object obj, object prefabObj, MaterialReferenceInfo entry, StringBuilder errors) { if (errors == null) throw new ArgumentNullException("You must provide a valid errors parameter"); if (obj == null) { errors.AppendLine($"The given object to change material references is null"); return false; } bool ok = true; foreach (var materialAccessor in entry.materialAccessors) { bool reassignOk = (prefabObj != null) ? ReassignMaterialsFromInstaceIfOverriden(obj, prefabObj, materialAccessor.member, materialAccessor.isArray, errors): ReassignMaterialsFromInstance(obj, materialAccessor.member, materialAccessor.isArray); if (!reassignOk) { ok = false; errors.AppendLine($"Unable to change material on {entry.type} with property {materialAccessor.member.Name}"); } } return ok; } public bool ReassignUnityObjectMaterials(Object obj, StringBuilder errors) { if (obj == null) { return false; } bool reassignOk = true; if (obj is GameObject go) { // Iterate over all components, and reassign materials reassignOk = ReassignGameObjectMaterials(go, errors); } // Any other type, just get the mappings for that type and assing it through reflection else if (MaterialReferenceBuilder.TryGetReferenceInfoFromType(obj.GetType(), out var entry)) { reassignOk = ReassignMaterials(obj, null, entry, errors); } if (reassignOk) { // Make sure the changes get saved EditorUtility.SetDirty(obj); EditorSceneManager.SaveScene(SceneManager.GetActiveScene()); } else errors.AppendLine($"Could not reassign materials of {obj} with {obj.GetType()} type."); return reassignOk; } } }