HierarchyProcessor.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. using System.Linq;
  2. using Newtonsoft.Json.Linq;
  3. using System.Collections.Generic;
  4. using AssetBank.Editor.SchemaConverter.Data.Input;
  5. using AssetBank.Editor.SchemaConverter.Data.Output;
  6. namespace AssetBank.Editor.SchemaConverter.Processors
  7. {
  8. /// <summary>
  9. /// Handles the logic for building the scene hierarchy from GameObject nodes.
  10. /// </summary>
  11. public class HierarchyProcessor
  12. {
  13. private readonly List<string> _warnings = new();
  14. private IReadOnlyList<OriginalNode> _allNodes;
  15. private Dictionary<string, HierarchicalNode> _unappendedPrefabs;
  16. private HashSet<string> _visitedGameObjects;
  17. private Dictionary<string, HierarchicalNode> _nodeMap;
  18. private static readonly HashSet<string> s_RedundantTransformKeys = new()
  19. {
  20. "m_Father", "m_Children", "m_PrefabInstance", "m_CorrespondingSourceObject", "m_PrefabAsset"
  21. };
  22. public (List<HierarchicalNode> rootNodes, List<string> warnings, HashSet<string> consumedAnchorIds) Process(
  23. IReadOnlyList<OriginalNode> allNodes,
  24. IReadOnlyDictionary<string, OriginalNode> prefabInstances,
  25. List<string> prefabGuids)
  26. {
  27. _allNodes = allNodes;
  28. _visitedGameObjects = new HashSet<string>();
  29. _nodeMap = new Dictionary<string, HierarchicalNode>();
  30. var consumedAnchorIds = new HashSet<string>();
  31. var rootHierarchicalNodes = new List<HierarchicalNode>();
  32. var allGameObjects = allNodes.Where(n => n.type_id == "1").ToList();
  33. // 1. Identify all Transforms that are linked to a PrefabInstance and create a lookup for them.
  34. var allTransforms = allNodes.Where(n => n.type_id == "4").ToList();
  35. _unappendedPrefabs = new Dictionary<string, HierarchicalNode>();
  36. foreach (var transformNode in allTransforms)
  37. {
  38. var prefabInstanceId = transformNode.data?["Transform"]?["m_PrefabInstance"]?["fileID"]?.ToString();
  39. if (!string.IsNullOrEmpty(prefabInstanceId) && prefabInstances.TryGetValue(prefabInstanceId, out var prefabInstance))
  40. {
  41. var guid = prefabInstance.data?["PrefabInstance"]?["m_SourcePrefab"]?["guid"]?.ToString();
  42. var guidIndex = prefabGuids.IndexOf(guid);
  43. if (guidIndex != -1)
  44. {
  45. if (!_nodeMap.TryGetValue(transformNode.anchor_id, out var prefabNode))
  46. {
  47. prefabNode = new HierarchicalNode
  48. {
  49. anchor_id = transformNode.anchor_id,
  50. type_id = transformNode.type_id,
  51. };
  52. _nodeMap[transformNode.anchor_id] = prefabNode;
  53. }
  54. prefabNode.prefab_guid_index = (ulong)guidIndex;
  55. PopulateNodeFields(prefabNode, transformNode);
  56. _unappendedPrefabs[transformNode.anchor_id] = prefabNode;
  57. consumedAnchorIds.Add(transformNode.anchor_id);
  58. }
  59. else if (!string.IsNullOrEmpty(guid))
  60. {
  61. _warnings.Add($"Found prefab with GUID '{guid}' but it was not in the provided GUID list.");
  62. }
  63. }
  64. }
  65. // 2. Identify and create nodes for all explicit root GameObjects.
  66. foreach (var goNode in allGameObjects)
  67. {
  68. consumedAnchorIds.Add(goNode.anchor_id);
  69. var transformComponent = GetTransformComponent(goNode);
  70. var fatherId = GetFatherIdFromTransform(transformComponent);
  71. if (fatherId == "0")
  72. {
  73. if (!_nodeMap.TryGetValue(goNode.anchor_id, out var rootNode))
  74. {
  75. rootNode = new HierarchicalNode { anchor_id = goNode.anchor_id, type_id = goNode.type_id };
  76. PopulateNodeFields(rootNode, goNode);
  77. _nodeMap[goNode.anchor_id] = rootNode;
  78. }
  79. rootHierarchicalNodes.Add(rootNode);
  80. _visitedGameObjects.Add(rootNode.anchor_id);
  81. }
  82. }
  83. // 3. Recursively build the hierarchy for each root GameObject.
  84. // Create a copy for iteration as the collection will be modified.
  85. foreach (var rootNode in rootHierarchicalNodes.ToList())
  86. {
  87. BuildHierarchyRecursive(rootNode.anchor_id, allGameObjects);
  88. }
  89. // 4. Iteratively process all unvisited GameObjects.
  90. bool madeChangesInPass;
  91. do
  92. {
  93. madeChangesInPass = false;
  94. var unvisitedGameObjects = allGameObjects.Where(go => !_visitedGameObjects.Contains(go.anchor_id)).ToList();
  95. foreach (var goNode in unvisitedGameObjects)
  96. {
  97. var transformComponent = GetTransformComponent(goNode);
  98. var fatherId = GetFatherIdFromTransform(transformComponent);
  99. var parentNode = FindParentNode(fatherId);
  100. if (parentNode != null)
  101. {
  102. if (!_nodeMap.TryGetValue(goNode.anchor_id, out var childNode))
  103. {
  104. childNode = new HierarchicalNode { anchor_id = goNode.anchor_id, type_id = goNode.type_id };
  105. PopulateNodeFields(childNode, goNode);
  106. _nodeMap[childNode.anchor_id] = childNode;
  107. }
  108. if (!parentNode.children.Contains(childNode))
  109. {
  110. parentNode.children.Add(childNode);
  111. }
  112. _visitedGameObjects.Add(childNode.anchor_id);
  113. BuildHierarchyRecursive(childNode.anchor_id, allGameObjects);
  114. madeChangesInPass = true;
  115. }
  116. }
  117. } while (madeChangesInPass);
  118. // 5. Any remaining unvisited nodes are true orphans; treat them as roots.
  119. var finalUnvisited = allGameObjects.Where(go => !_visitedGameObjects.Contains(go.anchor_id)).ToList();
  120. foreach (var goNode in finalUnvisited)
  121. {
  122. if (!_nodeMap.TryGetValue(goNode.anchor_id, out var rootNode))
  123. {
  124. rootNode = new HierarchicalNode { anchor_id = goNode.anchor_id, type_id = goNode.type_id };
  125. PopulateNodeFields(rootNode, goNode);
  126. _nodeMap[rootNode.anchor_id] = rootNode;
  127. }
  128. rootNode.is_orphan = true;
  129. rootHierarchicalNodes.Add(rootNode);
  130. _visitedGameObjects.Add(rootNode.anchor_id);
  131. _warnings.Add($"GameObject '{goNode.anchor_id}' was treated as an orphan root.");
  132. BuildHierarchyRecursive(rootNode.anchor_id, allGameObjects);
  133. }
  134. // 5b. Prune false roots: remove any nodes from the root list that are actually children of other nodes.
  135. var allChildren = new HashSet<HierarchicalNode>();
  136. var queue = new Queue<HierarchicalNode>(rootHierarchicalNodes);
  137. var visitedForPruning = new HashSet<HierarchicalNode>();
  138. while (queue.Count > 0)
  139. {
  140. var node = queue.Dequeue();
  141. if (!visitedForPruning.Add(node)) continue;
  142. foreach (var child in node.children)
  143. {
  144. allChildren.Add(child);
  145. queue.Enqueue(child);
  146. }
  147. }
  148. rootHierarchicalNodes.RemoveAll(node => allChildren.Contains(node));
  149. // 6. Update nodes that are implicitly prefabs.
  150. UpdateImplicitPrefabInstances(rootHierarchicalNodes, prefabInstances, prefabGuids);
  151. // 7. Final Cleanup Pass
  152. CleanupNodes(rootHierarchicalNodes);
  153. return (rootHierarchicalNodes, _warnings, consumedAnchorIds);
  154. }
  155. private void BuildHierarchyRecursive(string parentAnchorId, IReadOnlyList<OriginalNode> allGameObjects)
  156. {
  157. if (!_nodeMap.TryGetValue(parentAnchorId, out var parentNode)) return;
  158. var parentOriginalNode = _allNodes.FirstOrDefault(n => n.anchor_id == parentAnchorId);
  159. if (parentOriginalNode == null) return;
  160. var parentTransformToken = GetTransformComponent(parentOriginalNode);
  161. if (parentOriginalNode.type_id == "4")
  162. {
  163. parentTransformToken = parentOriginalNode.data;
  164. }
  165. var childrenIds = parentTransformToken?["Transform"]?["m_Children"]?
  166. .Select(c => c["fileID"]?.ToString())
  167. .Where(id => !string.IsNullOrEmpty(id))
  168. .ToList();
  169. if (childrenIds == null) return;
  170. foreach (var childId in childrenIds)
  171. {
  172. var childOriginalNode = allGameObjects.FirstOrDefault(go => GetTransformId(go) == childId);
  173. if (childOriginalNode != null)
  174. {
  175. if (!_nodeMap.TryGetValue(childOriginalNode.anchor_id, out var childNode))
  176. {
  177. childNode = new HierarchicalNode { anchor_id = childOriginalNode.anchor_id, type_id = childOriginalNode.type_id };
  178. PopulateNodeFields(childNode, childOriginalNode);
  179. _nodeMap[childNode.anchor_id] = childNode;
  180. }
  181. if (!parentNode.children.Contains(childNode))
  182. {
  183. parentNode.children.Add(childNode);
  184. }
  185. if (_visitedGameObjects.Add(childNode.anchor_id))
  186. {
  187. BuildHierarchyRecursive(childNode.anchor_id, allGameObjects);
  188. }
  189. }
  190. else if (_unappendedPrefabs.TryGetValue(childId, out var prefabChildNode))
  191. {
  192. if (!parentNode.children.Contains(prefabChildNode))
  193. {
  194. parentNode.children.Add(prefabChildNode);
  195. }
  196. _unappendedPrefabs.Remove(childId);
  197. BuildHierarchyRecursive(prefabChildNode.anchor_id, allGameObjects);
  198. }
  199. }
  200. }
  201. private static void UpdateImplicitPrefabInstances(
  202. IEnumerable<HierarchicalNode> nodes,
  203. IReadOnlyDictionary<string, OriginalNode> prefabInstances,
  204. List<string> prefabGuids)
  205. {
  206. if (nodes == null) return;
  207. foreach (var node in nodes)
  208. {
  209. // Only try to update the node if its prefab status is unknown.
  210. if (!node.prefab_guid_index.HasValue)
  211. {
  212. string prefabInstanceFileId = null;
  213. if (node.type_id == "1" && node.Fields.TryGetValue("m_Component", out var componentsToken) && componentsToken is JArray components)
  214. {
  215. foreach (var component in components)
  216. {
  217. var fileID = component?["Transform"]?["m_PrefabInstance"]?["fileID"]?.ToString();
  218. if (!string.IsNullOrEmpty(fileID) && fileID != "0")
  219. {
  220. prefabInstanceFileId = fileID;
  221. break;
  222. }
  223. }
  224. }
  225. if (prefabInstanceFileId != null)
  226. {
  227. if (prefabInstances.TryGetValue(prefabInstanceFileId, out var prefabInstanceNode))
  228. {
  229. var guid = prefabInstanceNode.data?["PrefabInstance"]?["m_SourcePrefab"]?["guid"]?.ToString();
  230. if (!string.IsNullOrEmpty(guid))
  231. {
  232. var guidIndex = prefabGuids.IndexOf(guid);
  233. if (guidIndex != -1)
  234. {
  235. node.prefab_guid_index = (ulong)guidIndex;
  236. }
  237. }
  238. }
  239. }
  240. }
  241. // ALWAYS recurse, regardless of the parent's status.
  242. if (node.children.Any())
  243. {
  244. UpdateImplicitPrefabInstances(node.children, prefabInstances, prefabGuids);
  245. }
  246. }
  247. }
  248. private static void PopulateNodeFields(HierarchicalNode node, OriginalNode originalNode)
  249. {
  250. JObject dataBlock = null;
  251. if (originalNode.type_id == "1")
  252. {
  253. dataBlock = originalNode.data?["GameObject"] as JObject;
  254. }
  255. else if (originalNode.type_id == "4")
  256. {
  257. dataBlock = originalNode.data;
  258. }
  259. if (dataBlock == null) return;
  260. foreach (var property in dataBlock.Properties())
  261. {
  262. node.Fields[property.Name] = property.Value;
  263. }
  264. }
  265. private static void CleanupNodes(IEnumerable<HierarchicalNode> nodes)
  266. {
  267. foreach (var node in nodes)
  268. {
  269. if (node.Fields.TryGetValue("Transform", out var transformToken) && transformToken is JObject transformObj)
  270. {
  271. foreach (var key in s_RedundantTransformKeys)
  272. {
  273. transformObj.Remove(key);
  274. }
  275. }
  276. if (node.Fields.TryGetValue("m_Component", out var componentsToken) && componentsToken is JArray components)
  277. {
  278. foreach (var component in components)
  279. {
  280. if (component["Transform"] is JObject cTransform)
  281. {
  282. foreach (var key in s_RedundantTransformKeys)
  283. {
  284. cTransform.Remove(key);
  285. }
  286. }
  287. }
  288. }
  289. if (node.children.Any())
  290. {
  291. CleanupNodes(node.children);
  292. }
  293. }
  294. }
  295. private static JToken GetTransformComponent(OriginalNode goNode)
  296. {
  297. return goNode.data?["GameObject"]?["m_Component"]?.FirstOrDefault(c => c["Transform"] != null);
  298. }
  299. private static string GetTransformId(OriginalNode goNode)
  300. {
  301. return GetTransformComponent(goNode)?["anchor_id"]?.ToString();
  302. }
  303. private static string GetFatherIdFromTransform(JToken transformComponent)
  304. {
  305. return transformComponent?["Transform"]?["m_Father"]?["fileID"]?.ToString() ?? "0";
  306. }
  307. private HierarchicalNode FindParentNode(string fatherTransformId)
  308. {
  309. foreach (var node in _nodeMap.Values)
  310. {
  311. if (node.type_id == "1")
  312. {
  313. var originalNode = _allNodes.FirstOrDefault(n => n.anchor_id == node.anchor_id);
  314. if (originalNode == null) continue;
  315. var transformId = GetTransformId(originalNode);
  316. if (transformId == fatherTransformId)
  317. {
  318. return node;
  319. }
  320. }
  321. else if (node.type_id == "4")
  322. {
  323. if (node.anchor_id == fatherTransformId)
  324. {
  325. return node;
  326. }
  327. }
  328. }
  329. return null;
  330. }
  331. }
  332. }