using System; using System.Collections.Generic; using System.Linq; using nadena.dev.modular_avatar.animation; using nadena.dev.modular_avatar.editor.ErrorReporting; using UnityEditor; using UnityEngine; namespace nadena.dev.modular_avatar.core.editor { using UnityObject = UnityEngine.Object; // ReSharper disable once RedundantUsingDirective using Object = System.Object; internal class ReplaceObjectPass { private readonly ndmf.BuildContext _buildContext; public ReplaceObjectPass(ndmf.BuildContext context) { _buildContext = context; } struct Reference { public UnityObject Source; public string PropPath; } public void Process() { var avatarRootTransform = _buildContext.AvatarRootTransform; var replacementComponents = avatarRootTransform.GetComponentsInChildren(true); if (replacementComponents.Length == 0) return; // Build an index of object references within the avatar that we might need to fix Dictionary> refIndex = BuildReferenceIndex(); Dictionary replacements = new Dictionary(); foreach (var component in replacementComponents) { var targetObject = component.targetObject?.Get(avatarRootTransform); if (targetObject == null) { BuildReport.LogFatal("error.replace_object.null_target", new string[0], component, targetObject); UnityObject.DestroyImmediate(component.gameObject); continue; } if (component.transform.GetComponentsInParent().Contains(targetObject.transform)) { BuildReport.LogFatal("error.replace_object.parent_of_target", new string[0], component, targetObject); UnityObject.DestroyImmediate(component.gameObject); continue; } if (replacements.TryGetValue(targetObject, out var existingReplacement)) { BuildReport.LogFatal("error.replace_object.replacing_replacement", new string[0], component, existingReplacement.Item1); UnityObject.DestroyImmediate(component); continue; } replacements[targetObject] = (component, component.gameObject); } // Execute replacement. For now, we reparent children. // TODO: Handle replacing recursively. foreach (var kvp in replacements) { var original = kvp.Key; var replacement = kvp.Value.Item2; replacement.transform.SetParent(original.transform.parent, true); var siblingIndex = original.transform.GetSiblingIndex(); // Move children of original parent (the List needs to be cloned first because it is being modified) List children = new List(); foreach (Transform child in original.transform) { children.Add(child); } foreach (Transform child in children) { child.SetParent(replacement.transform, true); } // Update property references foreach (var refKey in GetIndexedComponents(original)) { var (component, index) = refKey; if (!refIndex.TryGetValue(component, out var references)) { continue; } UnityObject newValue = null; if (component is GameObject) { newValue = replacement; } else { var replacementCandidates = replacement.GetComponents(component.GetType()); if (replacementCandidates.Length > index) { newValue = replacementCandidates[index]; } } foreach (var reference in references) { SerializedObject so = new SerializedObject(reference.Source); SerializedProperty prop = so.FindProperty(reference.PropPath); prop.objectReferenceValue = newValue; so.ApplyModifiedPropertiesWithoutUndo(); } } _buildContext.Extension().PathMappings .ReplaceObject(original, replacement); // Destroy original UnityObject.DestroyImmediate(original); replacement.transform.SetSiblingIndex(siblingIndex); } } private IEnumerable<(UnityObject, int)> GetIndexedComponents(GameObject original) { yield return (original, -1); Dictionary componentTypeIndex = new Dictionary(); foreach (var component in original.GetComponents()) { if (!componentTypeIndex.TryGetValue(component.GetType(), out int index)) { index = 0; } componentTypeIndex[component.GetType()] = index + 1; yield return (component, index); } } private Dictionary> BuildReferenceIndex() { Dictionary> refIndex = new Dictionary>(); IndexObject(_buildContext.AvatarRootObject); return refIndex; void IndexObject(GameObject obj) { foreach (Transform child in obj.transform) { IndexObject(child.gameObject); } Dictionary componentIndex = new Dictionary(); foreach (Component c in obj.GetComponents(typeof(Component))) { if (c == null) continue; if (c is Transform) continue; if (!componentIndex.TryGetValue(c.GetType(), out int index)) index = 0; componentIndex[c.GetType()] = index + 1; var so = new SerializedObject(c); var sp = so.GetIterator(); bool enterChildren = true; while (sp.Next(enterChildren)) { enterChildren = true; if (sp.propertyType == SerializedPropertyType.String) enterChildren = false; if (sp.propertyType != SerializedPropertyType.ObjectReference) continue; if (sp.objectReferenceValue == null) continue; string path = sp.propertyPath; if (path == "m_GameObject") continue; Reference reference; if (sp.objectReferenceValue is GameObject) { // ok } else if (sp.objectReferenceValue is Component) { // ok } else { continue; } reference.Source = c; reference.PropPath = sp.propertyPath; var refKey = sp.objectReferenceValue; if (!refIndex.TryGetValue(refKey, out var refList)) { refList = new List(); refIndex[refKey] = refList; } refList.Add(reference); } } } } } }