123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381 |
- using System.Linq;
- using Newtonsoft.Json.Linq;
- using System.Collections.Generic;
- using AssetBank.Editor.SchemaConverter.Data.Input;
- using AssetBank.Editor.SchemaConverter.Data.Output;
- namespace AssetBank.Editor.SchemaConverter.Processors
- {
- /// <summary>
- /// Handles the logic for building the scene hierarchy from GameObject nodes.
- /// </summary>
- public class HierarchyProcessor
- {
- private readonly List<string> _warnings = new();
- private IReadOnlyList<OriginalNode> _allNodes;
- private Dictionary<string, HierarchicalNode> _unappendedPrefabs;
- private HashSet<string> _visitedGameObjects;
- private Dictionary<string, HierarchicalNode> _nodeMap;
- private static readonly HashSet<string> s_RedundantTransformKeys = new()
- {
- "m_Father", "m_Children", "m_PrefabInstance", "m_CorrespondingSourceObject", "m_PrefabAsset"
- };
- public (List<HierarchicalNode> rootNodes, List<string> warnings, HashSet<string> consumedAnchorIds) Process(
- IReadOnlyList<OriginalNode> allNodes,
- IReadOnlyDictionary<string, OriginalNode> prefabInstances,
- List<string> prefabGuids)
- {
- _allNodes = allNodes;
- _visitedGameObjects = new HashSet<string>();
- _nodeMap = new Dictionary<string, HierarchicalNode>();
-
- var consumedAnchorIds = new HashSet<string>();
- var rootHierarchicalNodes = new List<HierarchicalNode>();
- var allGameObjects = allNodes.Where(n => n.type_id == "1").ToList();
- // 1. Identify all Transforms that are linked to a PrefabInstance and create a lookup for them.
- var allTransforms = allNodes.Where(n => n.type_id == "4").ToList();
- _unappendedPrefabs = new Dictionary<string, HierarchicalNode>();
- foreach (var transformNode in allTransforms)
- {
- var prefabInstanceId = transformNode.data?["Transform"]?["m_PrefabInstance"]?["fileID"]?.ToString();
- if (!string.IsNullOrEmpty(prefabInstanceId) && prefabInstances.TryGetValue(prefabInstanceId, out var prefabInstance))
- {
- var guid = prefabInstance.data?["PrefabInstance"]?["m_SourcePrefab"]?["guid"]?.ToString();
- var guidIndex = prefabGuids.IndexOf(guid);
- if (guidIndex != -1)
- {
- if (!_nodeMap.TryGetValue(transformNode.anchor_id, out var prefabNode))
- {
- prefabNode = new HierarchicalNode
- {
- anchor_id = transformNode.anchor_id,
- type_id = transformNode.type_id,
- };
- _nodeMap[transformNode.anchor_id] = prefabNode;
- }
- prefabNode.prefab_guid_index = (ulong)guidIndex;
- PopulateNodeFields(prefabNode, transformNode);
- _unappendedPrefabs[transformNode.anchor_id] = prefabNode;
- consumedAnchorIds.Add(transformNode.anchor_id);
- }
- else if (!string.IsNullOrEmpty(guid))
- {
- _warnings.Add($"Found prefab with GUID '{guid}' but it was not in the provided GUID list.");
- }
- }
- }
- // 2. Identify and create nodes for all explicit root GameObjects.
- foreach (var goNode in allGameObjects)
- {
- consumedAnchorIds.Add(goNode.anchor_id);
- var transformComponent = GetTransformComponent(goNode);
- var fatherId = GetFatherIdFromTransform(transformComponent);
- if (fatherId == "0")
- {
- if (!_nodeMap.TryGetValue(goNode.anchor_id, out var rootNode))
- {
- rootNode = new HierarchicalNode { anchor_id = goNode.anchor_id, type_id = goNode.type_id };
- PopulateNodeFields(rootNode, goNode);
- _nodeMap[goNode.anchor_id] = rootNode;
- }
- rootHierarchicalNodes.Add(rootNode);
- _visitedGameObjects.Add(rootNode.anchor_id);
- }
- }
- // 3. Recursively build the hierarchy for each root GameObject.
- // Create a copy for iteration as the collection will be modified.
- foreach (var rootNode in rootHierarchicalNodes.ToList())
- {
- BuildHierarchyRecursive(rootNode.anchor_id, allGameObjects);
- }
-
- // 4. Iteratively process all unvisited GameObjects.
- bool madeChangesInPass;
- do
- {
- madeChangesInPass = false;
- var unvisitedGameObjects = allGameObjects.Where(go => !_visitedGameObjects.Contains(go.anchor_id)).ToList();
- foreach (var goNode in unvisitedGameObjects)
- {
- var transformComponent = GetTransformComponent(goNode);
- var fatherId = GetFatherIdFromTransform(transformComponent);
- var parentNode = FindParentNode(fatherId);
- if (parentNode != null)
- {
- if (!_nodeMap.TryGetValue(goNode.anchor_id, out var childNode))
- {
- childNode = new HierarchicalNode { anchor_id = goNode.anchor_id, type_id = goNode.type_id };
- PopulateNodeFields(childNode, goNode);
- _nodeMap[childNode.anchor_id] = childNode;
- }
-
- if (!parentNode.children.Contains(childNode))
- {
- parentNode.children.Add(childNode);
- }
- _visitedGameObjects.Add(childNode.anchor_id);
-
- BuildHierarchyRecursive(childNode.anchor_id, allGameObjects);
- madeChangesInPass = true;
- }
- }
- } while (madeChangesInPass);
- // 5. Any remaining unvisited nodes are true orphans; treat them as roots.
- var finalUnvisited = allGameObjects.Where(go => !_visitedGameObjects.Contains(go.anchor_id)).ToList();
- foreach (var goNode in finalUnvisited)
- {
- if (!_nodeMap.TryGetValue(goNode.anchor_id, out var rootNode))
- {
- rootNode = new HierarchicalNode { anchor_id = goNode.anchor_id, type_id = goNode.type_id };
- PopulateNodeFields(rootNode, goNode);
- _nodeMap[rootNode.anchor_id] = rootNode;
- }
-
- rootNode.is_orphan = true;
- rootHierarchicalNodes.Add(rootNode);
- _visitedGameObjects.Add(rootNode.anchor_id);
- _warnings.Add($"GameObject '{goNode.anchor_id}' was treated as an orphan root.");
-
- BuildHierarchyRecursive(rootNode.anchor_id, allGameObjects);
- }
-
- // 5b. Prune false roots: remove any nodes from the root list that are actually children of other nodes.
- var allChildren = new HashSet<HierarchicalNode>();
- var queue = new Queue<HierarchicalNode>(rootHierarchicalNodes);
- var visitedForPruning = new HashSet<HierarchicalNode>();
- while (queue.Count > 0)
- {
- var node = queue.Dequeue();
- if (!visitedForPruning.Add(node)) continue;
- foreach (var child in node.children)
- {
- allChildren.Add(child);
- queue.Enqueue(child);
- }
- }
- rootHierarchicalNodes.RemoveAll(node => allChildren.Contains(node));
-
- // 6. Update nodes that are implicitly prefabs.
- UpdateImplicitPrefabInstances(rootHierarchicalNodes, prefabInstances, prefabGuids);
-
- // 7. Final Cleanup Pass
- CleanupNodes(rootHierarchicalNodes);
- return (rootHierarchicalNodes, _warnings, consumedAnchorIds);
- }
- private void BuildHierarchyRecursive(string parentAnchorId, IReadOnlyList<OriginalNode> allGameObjects)
- {
- if (!_nodeMap.TryGetValue(parentAnchorId, out var parentNode)) return;
- var parentOriginalNode = _allNodes.FirstOrDefault(n => n.anchor_id == parentAnchorId);
- if (parentOriginalNode == null) return;
-
- var parentTransformToken = GetTransformComponent(parentOriginalNode);
- if (parentOriginalNode.type_id == "4")
- {
- parentTransformToken = parentOriginalNode.data;
- }
- var childrenIds = parentTransformToken?["Transform"]?["m_Children"]?
- .Select(c => c["fileID"]?.ToString())
- .Where(id => !string.IsNullOrEmpty(id))
- .ToList();
- if (childrenIds == null) return;
- foreach (var childId in childrenIds)
- {
- var childOriginalNode = allGameObjects.FirstOrDefault(go => GetTransformId(go) == childId);
- if (childOriginalNode != null)
- {
- if (!_nodeMap.TryGetValue(childOriginalNode.anchor_id, out var childNode))
- {
- childNode = new HierarchicalNode { anchor_id = childOriginalNode.anchor_id, type_id = childOriginalNode.type_id };
- PopulateNodeFields(childNode, childOriginalNode);
- _nodeMap[childNode.anchor_id] = childNode;
- }
-
- if (!parentNode.children.Contains(childNode))
- {
- parentNode.children.Add(childNode);
- }
-
- if (_visitedGameObjects.Add(childNode.anchor_id))
- {
- BuildHierarchyRecursive(childNode.anchor_id, allGameObjects);
- }
- }
- else if (_unappendedPrefabs.TryGetValue(childId, out var prefabChildNode))
- {
- if (!parentNode.children.Contains(prefabChildNode))
- {
- parentNode.children.Add(prefabChildNode);
- }
- _unappendedPrefabs.Remove(childId);
- BuildHierarchyRecursive(prefabChildNode.anchor_id, allGameObjects);
- }
- }
- }
-
- private static void UpdateImplicitPrefabInstances(
- IEnumerable<HierarchicalNode> nodes,
- IReadOnlyDictionary<string, OriginalNode> prefabInstances,
- List<string> prefabGuids)
- {
- if (nodes == null) return;
- foreach (var node in nodes)
- {
- // Only try to update the node if its prefab status is unknown.
- if (!node.prefab_guid_index.HasValue)
- {
- string prefabInstanceFileId = null;
-
- if (node.type_id == "1" && node.Fields.TryGetValue("m_Component", out var componentsToken) && componentsToken is JArray components)
- {
- foreach (var component in components)
- {
- var fileID = component?["Transform"]?["m_PrefabInstance"]?["fileID"]?.ToString();
- if (!string.IsNullOrEmpty(fileID) && fileID != "0")
- {
- prefabInstanceFileId = fileID;
- break;
- }
- }
- }
- if (prefabInstanceFileId != null)
- {
- if (prefabInstances.TryGetValue(prefabInstanceFileId, out var prefabInstanceNode))
- {
- var guid = prefabInstanceNode.data?["PrefabInstance"]?["m_SourcePrefab"]?["guid"]?.ToString();
- if (!string.IsNullOrEmpty(guid))
- {
- var guidIndex = prefabGuids.IndexOf(guid);
- if (guidIndex != -1)
- {
- node.prefab_guid_index = (ulong)guidIndex;
- }
- }
- }
- }
- }
-
- // ALWAYS recurse, regardless of the parent's status.
- if (node.children.Any())
- {
- UpdateImplicitPrefabInstances(node.children, prefabInstances, prefabGuids);
- }
- }
- }
- private static void PopulateNodeFields(HierarchicalNode node, OriginalNode originalNode)
- {
- JObject dataBlock = null;
- if (originalNode.type_id == "1")
- {
- dataBlock = originalNode.data?["GameObject"] as JObject;
- }
- else if (originalNode.type_id == "4")
- {
- dataBlock = originalNode.data;
- }
-
- if (dataBlock == null) return;
-
- foreach (var property in dataBlock.Properties())
- {
- node.Fields[property.Name] = property.Value;
- }
- }
- private static void CleanupNodes(IEnumerable<HierarchicalNode> nodes)
- {
- foreach (var node in nodes)
- {
- if (node.Fields.TryGetValue("Transform", out var transformToken) && transformToken is JObject transformObj)
- {
- foreach (var key in s_RedundantTransformKeys)
- {
- transformObj.Remove(key);
- }
- }
- if (node.Fields.TryGetValue("m_Component", out var componentsToken) && componentsToken is JArray components)
- {
- foreach (var component in components)
- {
- if (component["Transform"] is JObject cTransform)
- {
- foreach (var key in s_RedundantTransformKeys)
- {
- cTransform.Remove(key);
- }
- }
- }
- }
- if (node.children.Any())
- {
- CleanupNodes(node.children);
- }
- }
- }
- private static JToken GetTransformComponent(OriginalNode goNode)
- {
- return goNode.data?["GameObject"]?["m_Component"]?.FirstOrDefault(c => c["Transform"] != null);
- }
-
- private static string GetTransformId(OriginalNode goNode)
- {
- return GetTransformComponent(goNode)?["anchor_id"]?.ToString();
- }
- private static string GetFatherIdFromTransform(JToken transformComponent)
- {
- return transformComponent?["Transform"]?["m_Father"]?["fileID"]?.ToString() ?? "0";
- }
- private HierarchicalNode FindParentNode(string fatherTransformId)
- {
- foreach (var node in _nodeMap.Values)
- {
- if (node.type_id == "1")
- {
- var originalNode = _allNodes.FirstOrDefault(n => n.anchor_id == node.anchor_id);
- if (originalNode == null) continue;
-
- var transformId = GetTransformId(originalNode);
- if (transformId == fatherTransformId)
- {
- return node;
- }
- }
- else if (node.type_id == "4")
- {
- if (node.anchor_id == fatherTransformId)
- {
- return node;
- }
- }
- }
- return null;
- }
- }
- }
|