private void GuessChildRemapTargetsRecursive(ModelTreeNode node)
 {
     for (int nodeChildIndex = 0; nodeChildIndex < node.children.Count; nodeChildIndex++)
     {
         ModelTreeNode child = node.children[nodeChildIndex];
         if (child.remapTargetIsGuess)
         {
             if (child.isAddition)
             {
                 child.remapTarget = node.remapTarget;
                 continue; //additions never have children to guess remap targets for
             }
             else
             {
                 child.remapTarget = null;
                 //Scan for obj under remap target with same name as child obj
                 if (node.remapTarget != null)
                 {
                     for (int targetChildIndex = 0; targetChildIndex < node.remapTarget.transform.childCount; targetChildIndex++)
                     {
                         if (child.obj.name == node.remapTarget.transform.GetChild(targetChildIndex).name)
                         {
                             child.remapTarget = node.remapTarget.transform.GetChild(targetChildIndex).gameObject;
                             break;
                         }
                     }
                 }
             }
         }
         GuessChildRemapTargetsRecursive(child);
     }
 }
        //Copies the given node and attaches it to its remap target.
        //MethodsToInvoke is populated with MethodDefs for any methods in copied components with the OnModelReplaced attribute.
        //All performed remaps (modified and cloned components, duplicated gameobjects, and targeted non-additions) are added to the remaps dictionary
        void Remap(ModelTreeNode node, List <MethodDef> methodsToInvoke, Dictionary <UnityEngine.Object, UnityEngine.Object> remaps)
        {
            if (node.obj != null && node.remapTarget != null)
            {
                remaps[node.obj] = node.remapTarget;
            }
            if (!node.hasAdditions || !node.isAddition || node.remapTarget == null)
            {
                return;
            }
            if (node.comps != null)
            {
                //Copy components to target
                List <Component> copiedComponents = CopyComponents(node.comps, node.remapTarget, node.conflictResolutions, remaps);

                //Get on-replace functions
                foreach (Component copiedComp in copiedComponents)
                {
                    methodsToInvoke.AddRange(GetOnReplaceMethodDefs(copiedComp, node.obj));
                }
            }
            else
            {
                //Duplicate and parent dupe to target
                GameObject dupe = Instantiate(node.obj);
                dupe.name = node.obj.name;
                dupe.transform.SetParent(node.remapTarget.transform, false);
                //Collect remaps and OnModelReplaced funcs in dupe
                Stack <Transform> dupes     = new Stack <Transform>();
                Stack <Transform> originals = new Stack <Transform>();
                dupes.Push(dupe.transform);
                originals.Push(node.obj.transform);
                while (dupes.Count > 0)
                {
                    var dupeTrans = dupes.Pop();
                    var origTrans = originals.Pop();
                    remaps[origTrans.gameObject] = dupeTrans.gameObject;
                    Component[] dupeComps = dupeTrans.GetComponents <Component>();
                    Component[] origComps = origTrans.GetComponents <Component>();
                    for (int i = 0; i < dupeComps.Length; i++)
                    {
                        methodsToInvoke.AddRange(GetOnReplaceMethodDefs(dupeComps[i], origTrans.gameObject));
                        remaps[dupeComps[i]] = origComps[i];
                        //Sanity check / error out if Unity scrambles the component order (which it shouldnt)
                        if (dupeComps[i].GetType() != origComps[i].GetType())
                        {
                            throw new Exception("Component type mismatch");
                        }
                    }
                    foreach (Transform dupeChild in dupeTrans)
                    {
                        dupes.Push(dupeChild);
                    }
                    foreach (Transform origChild in origTrans)
                    {
                        originals.Push(origChild);
                    }
                }
            }
        }
 //Checks for conflicts in the tree rooted at the given node
 private void CheckConflictsRecursive(ModelTreeNode node)
 {
     if (node.UpdateConflicts())
     {
         Repaint();
     }
     for (int i = 0; i < node.children.Count; i++)
     {
         CheckConflictsRecursive(node.children[i]);
     }
 }
        //Recursively draws the hierarchy rooted at the given node, starting at the given indent level.
        //Returns true if the tree properties are valid for doing the copy operation, false otherwise.
        bool DrawAdditionTree(ModelTreeNode node, int indentLevel)
        {
            const int spacePerIndent = 12; //EditorGUI.indentLevel messes with field width, do indenting ourselves

            if (!node.hasAdditions)
            {
                //Don't draw branches with no additions in them
                return(true);
            }
            GUILayout.BeginHorizontal();
            GUILayout.Space(spacePerIndent * indentLevel);
            bool selfValid = true;

            //Components
            if (node.comps != null)
            {
                GUILayout.Space(spacePerIndent); //indent an extra level
                GUILayout.BeginVertical();
                selfValid = DrawAddition(node);
                DrawConflictResolution(node);
                GUILayout.EndVertical();
            }
            //Object
            else if (node.obj != null)
            {
                if (node.children.Count > 0)
                {
                    node.expanded = EditorGUILayout.Foldout(node.expanded, node.obj.name);
                    selfValid     = DrawRemapTargetField(node);
                    GUI.color     = Color.white;
                }
                else
                {
                    selfValid = DrawAddition(node);
                }
            }
            GUILayout.EndHorizontal();
            //Recurse on children
            bool childrenValid = true;

            if (node.expanded)
            {
                for (int i = 0; i < node.children.Count; i++)
                {
                    childrenValid &= DrawAdditionTree(node.children[i], indentLevel + 1);
                }
            }
            return(selfValid && childrenValid);
        }
 //Draws component conflict resolution UI for all conflicting types in given node
 private void DrawConflictResolution(ModelTreeNode node)
 {
     if (node.conflicts != null && node.conflicts.Count > 0)
     {
         GUILayout.Label("Some components exist on Source and Dest. Choose how to resolve conflicts.", EditorStyles.wordWrappedLabel);
         foreach (Type conflictType in node.conflicts)
         {
             if (!node.conflictResolutions.ContainsKey(conflictType))
             {
                 node.conflictResolutions[conflictType] = DefaultConflictResolution;
             }
             node.conflictResolutions[conflictType] = (CopyConflictMode)EditorGUILayout.EnumPopup(conflictType.Name, node.conflictResolutions[conflictType], GUILayout.MaxWidth(320));
         }
     }
 }
 //Tries to automatically fix a property which holds an objectreference to the old hierarchy
 //Returns true if a fix was applied, false otherwise
 bool FixCrossReference(SerializedProperty objProperty, Dictionary <UnityEngine.Object, UnityEngine.Object> remaps, Dictionary <Component, ModelTreeNode> compToNode)
 {
     //References to gameobjects we copied
     //References to components we copied
     //References to gameobjects with remap targets, even if they weren't copied due to not being additions
     if (remaps.ContainsKey(objProperty.objectReferenceValue))
     {
         objProperty.objectReferenceValue = remaps[objProperty.objectReferenceValue];
         objProperty.serializedObject.ApplyModifiedPropertiesWithoutUndo();
         return(true);
     }
     //References to components that weren't copied, if they are on gameobjects with remap targets
     //AND the user didn't specify this was a component conflict where DEST should be preserved
     else if (objProperty.objectReferenceValue is Component)
     {
         var comp = objProperty.objectReferenceValue as Component;
         if (remaps.ContainsKey(comp.gameObject))
         {
             Component[] srcComps  = comp.gameObject.GetComponents(comp.GetType());
             Component[] destComps = (remaps[comp.gameObject] as GameObject).GetComponents(comp.GetType());
             for (int i = 0; i < srcComps.Length && i < destComps.Length; i++)
             {
                 if (srcComps[i] == comp)
                 {
                     ModelTreeNode node = null;
                     compToNode.TryGetValue(comp, out node);
                     if (node == null ||
                         node.conflictResolutions == null ||
                         !node.conflictResolutions.ContainsKey(comp.GetType()) ||
                         node.conflictResolutions[comp.GetType()] != CopyConflictMode.KeepDest)
                     {
                         objProperty.objectReferenceValue = destComps[i];
                         objProperty.serializedObject.ApplyModifiedPropertiesWithoutUndo();
                         return(true);
                     }
                     return(false); //conflict mode was "keep dest"
                 }
             }
         }
     }
     return(false);
 }
        //Performs copy of all nodes in tree rooted at given node to their remapTargets, and invokes the OnModelReplaced function in any copied components.
        //(not *actually* recursive)
        void RemapAndInvokeRecursive(ModelTreeNode start)
        {
            refFixupMap = new Dictionary <UnityEngine.Object, UnityEngine.Object>();
            List <MethodDef>      methodsToInvoke = new List <MethodDef>();
            Stack <ModelTreeNode> nodes           = new Stack <ModelTreeNode>();

            nodes.Push(start);
            while (nodes.Count > 0)
            {
                ModelTreeNode node = nodes.Pop();
                Remap(node, methodsToInvoke, refFixupMap);
                foreach (ModelTreeNode c in node.children)
                {
                    if (node.hasAdditions)
                    {
                        nodes.Push(c);
                    }
                }
            }
            InvokeAll(methodsToInvoke);
        }
        private void OnGUI()
        {
            scrollPos = GUILayout.BeginScrollView(scrollPos);
            bool objsChanged = DrawTargetSelection();

            if (objsChanged)
            {
                treeRoot = null;
            }
            if (treeRoot == null && TargetsValid)
            {
                treeRoot = FindAdditions();
            }
            bool treeValid = false;

            if (TargetsValid)
            {
                CheckConflictsRecursive(treeRoot);
                GUILayout.Label("Assign targets in new hierarchy to attach additions to.", EditorStyles.wordWrappedLabel);
                treeValid = DrawAdditionTree(treeRoot, 0);
                if (!treeRoot.hasAdditions)
                {
                    EditorGUILayout.HelpBox("Found no additions to Source. Nothing to do here!", MessageType.Info);
                    treeValid = false;
                }
            }

            //Execute button
            GUI.enabled = TargetsValid && treeValid;
            if (GUILayout.Button("Copy to Targets"))
            {
                RemapAndInvokeRecursive(treeRoot);
                ProcessCrossReferencesInTree();
            }
            GUI.enabled = true;
            GUILayout.EndScrollView();
        }
        //Draws a node which is an addition.
        //Returns true if the node properties are valid for doing the copy operation, false otherwise.
        private bool DrawAddition(ModelTreeNode node)
        {
            string  label;
            Texture icon;

            //Determine label and icon
            if (node.comps == null)
            {
                label = node.obj.name;
                icon  = AssetPreview.GetMiniTypeThumbnail(typeof(GameObject));
            }
            else
            {
                StringBuilder compString = new StringBuilder();
                for (int i = 0; i < node.comps.Count; i++)
                {
                    compString.Append(node.comps[i].GetType().Name);
                    if (i < node.comps.Count - 1)
                    {
                        compString.Append(", ");
                    }
                }
                label = compString.ToString(); //could cache this, maybe
                icon  = AssetPreview.GetMiniTypeThumbnail(typeof(Component));
            }
            //Draw
            GUILayout.BeginHorizontal();
            GUILayout.BeginHorizontal(GUI.skin.box);
            EditorGUILayout.LabelField(new GUIContent(icon), GUILayout.Height(16), GUILayout.Width(16));
            EditorGUILayout.LabelField(label, EditorStyles.wordWrappedLabel, GUILayout.ExpandWidth(true));
            GUILayout.EndHorizontal();
            bool valid = DrawRemapTargetField(node);

            GUILayout.EndHorizontal();
            return(valid);
        }
        private ModelTreeNode FindAdditionsRecursive(GameObject original, GameObject modified)
        {
            bool hasAdditions = false;
            var  node         = new ModelTreeNode(modified, false);
            //Check components
            var              modifiedComponents = modified.GetComponents <Component>();
            HashSet <Type>   addedTypes         = new HashSet <Type>();
            List <Component> addedComponents    = new List <Component>();

            for (int i = 0; i < modifiedComponents.Length; i++)
            {
                Type compType = modifiedComponents[i].GetType();
                //Skip if already checked this type
                if (addedTypes.Contains(compType))
                {
                    continue;
                }
                //Add all extra components in modified to additions
                addedTypes.Add(compType);
                var modifiedOfType = modified.GetComponents(compType);
                var originalOfType = original.GetComponents(compType);
                for (int j = originalOfType.Length; j < modifiedOfType.Length; j++)
                {
                    addedComponents.Add(modifiedOfType[j]);
                    hasAdditions = true;
                }
            }
            if (addedComponents.Count > 0)
            {
                hasAdditions = true;
                node.children.Add(new ModelTreeNode(modified, addedComponents));
            }

            //Check child gameobjects
            int origChildIndex = 0;
            int modChildIndex  = 0;

            while (modChildIndex < modified.transform.childCount)
            {
                if (origChildIndex >= original.transform.childCount)
                {
                    //Ran out of objs in original to match against: All remaining objs in modified are additions
                    for (int i = modChildIndex; i < modified.transform.childCount; i++)
                    {
                        hasAdditions = true;
                        node.children.Add(new ModelTreeNode(modified.transform.GetChild(i).gameObject, true));
                    }
                    break;
                }
                else if (original.transform.GetChild(origChildIndex).name == modified.transform.GetChild(modChildIndex).name)
                {
                    //Matched object: Check the subtree for changes
                    var childNode = FindAdditionsRecursive(original.transform.GetChild(origChildIndex).gameObject, modified.transform.GetChild(modChildIndex).gameObject);
                    hasAdditions |= childNode.hasAdditions;
                    node.children.Add(childNode);
                    origChildIndex++;
                    modChildIndex++;
                }
                else
                {
                    //Mismatched object: obj is either an addition to modified or was deleted from original
                    //If it's an addition, we'll find the to the original later on in modified's child list.
                    int nextMatchIndex = -1;
                    //Scan ahead in modified for match
                    for (int i = modChildIndex + 1; i < modified.transform.childCount; i++)
                    {
                        if (original.transform.GetChild(origChildIndex).name == modified.transform.GetChild(i).name)
                        {
                            nextMatchIndex = i;
                            break;
                        }
                    }
                    //Match found: Everything we skipped over in modified while scanning is an addition
                    if (nextMatchIndex >= 0)
                    {
                        for (int i = modChildIndex; i < nextMatchIndex; i++)
                        {
                            node.children.Add(new ModelTreeNode(modified.transform.GetChild(i).gameObject, true));
                            hasAdditions = true;
                        }
                        modChildIndex = nextMatchIndex; //will process as a match next loop
                    }
                    //No match found: An object was deleted from original
                    else
                    {
                        origChildIndex++;
                    }
                }
            }
            node.hasAdditions = hasAdditions;
            return(node);
        }
        //Returns true if the remap target for the given node is valid for doing the copy operation, false otherwise.
        bool DrawRemapTargetField(ModelTreeNode node)
        {
            //Status lamp and target validation
            bool   valid = true;
            string statusTooltip;

            if (node.isAddition)
            {
                if (node.remapTarget == null)
                {
                    GUI.color     = Color.yellow;
                    statusTooltip = "No target specified: Addition will not be copied";
                }
                else if (treeRoot.remapTarget == null)
                {
                    GUI.color     = Color.yellow;
                    statusTooltip = "The root node must have a remap target specified to validate this target";
                    valid         = false;
                }
                else if (!node.remapTarget.transform.IsChildOf(treeRoot.remapTarget.transform))
                {
                    GUI.color     = Color.red;
                    statusTooltip = "Target must be a descendent of the root node's target";
                    valid         = false;
                }
                else
                {
                    GUI.color     = Color.green;
                    statusTooltip = "Addition will be copied and attached to specified target";
                }
            }
            else
            {
                if (node == treeRoot && node.remapTarget == null)
                {
                    GUI.color     = Color.red;
                    statusTooltip = "The root node must have a remap target specified.";
                    valid         = false;
                }
                else
                {
                    GUI.color     = Color.grey;
                    statusTooltip = "This is not an addition and will not be copied. Set this target to give hints for auto-filling other targets.";
                }
            }
            GUILayout.Label(new GUIContent("", statusTooltip), EditorGUIUtility.GetBuiltinSkin(EditorSkin.Inspector).GetStyle("radio"), GUILayout.Width(16)); //EditorSkin.Inspector = "Light"/Personal skin
            GUI.color = Color.white;
            //Object field
            EditorStyles.objectField.fontStyle = node.remapTargetIsGuess ? FontStyle.Normal : FontStyle.Bold;
            EditorGUI.BeginChangeCheck();
            Rect controlRect = EditorGUILayout.GetControlRect(GUILayout.Width(200));

            node.remapTarget = EditorGUI.ObjectField(controlRect, node.remapTarget, typeof(GameObject), true) as GameObject;
            EditorStyles.objectField.fontStyle = FontStyle.Normal;
            //Context click for "clear" menu
            if (Event.current.type == EventType.ContextClick && controlRect.Contains(Event.current.mousePosition))
            {
                GenericMenu menu = new GenericMenu();
                menu.AddItem(new GUIContent("Clear"), false, ResetRemapTarget, node);
                menu.ShowAsContext();
                Event.current.Use();
            }
            //Re-guess tree from this point if remap target changed
            if (EditorGUI.EndChangeCheck())
            {
                node.remapTargetIsGuess = false;
                GuessChildRemapTargetsRecursive(node);
            }
            return(valid);
        }