using System.Collections.ObjectModel; using Ghost.Entities; namespace Ghost.Editor.Core.SceneGraph; /// /// SceneGraph is the editor's view-model over the ECS runtime data. /// It provides a hierarchical representation of scenes and entities for UI rendering. /// /// This is editor-only and does not exist at runtime. /// public class SceneGraph { /// /// All scenes currently loaded in the editor world. /// public ObservableCollection Scenes { get; } /// /// Reference to the editor world containing ECS data. /// private readonly World _editorWorld; /// /// Cache: map from global entity ID to entity node for O(1) lookups. /// private Dictionary _entityNodeMap; /// /// Cache: map from scene ID to scene node for O(1) lookups. /// private Dictionary _sceneNodeMap; public SceneGraph(World editorWorld) { _editorWorld = editorWorld ?? throw new ArgumentNullException(nameof(editorWorld)); Scenes = new ObservableCollection(); _entityNodeMap = new Dictionary(); _sceneNodeMap = new Dictionary(); } /// /// Adds a scene to the scene graph. /// public SceneNode AddScene(string name, short sceneId, Guid? sceneGuid = null) { if (_sceneNodeMap.ContainsKey(sceneId)) { throw new InvalidOperationException($"Scene with ID {sceneId} already exists in the graph."); } var sceneNode = new SceneNode(name, sceneId, sceneGuid); Scenes.Add(sceneNode); _sceneNodeMap[sceneId] = sceneNode; return sceneNode; } /// /// Removes a scene from the scene graph. /// public bool RemoveScene(short sceneId) { if (!_sceneNodeMap.TryGetValue(sceneId, out var sceneNode)) { return false; } // Remove all entity nodes in this scene foreach (var entityNode in sceneNode.GetAllDescendants()) { _entityNodeMap.Remove(entityNode.EntityId); } Scenes.Remove(sceneNode); _sceneNodeMap.Remove(sceneId); return true; } /// /// Gets a scene node by its scene ID. /// public SceneNode? GetSceneNode(short sceneId) { _sceneNodeMap.TryGetValue(sceneId, out var sceneNode); return sceneNode; } /// /// Adds an entity node to a scene. /// If parentEntityId is valid, adds it as a child of that entity. /// Otherwise, adds it as a root entity in the scene. /// public EntityNode AddEntity(short sceneId, string name, Entity entityId, Entity parentEntityId = default) { var sceneNode = GetSceneNode(sceneId); if (sceneNode == null) { throw new InvalidOperationException($"Scene with ID {sceneId} not found."); } var entityNode = new EntityNode(name, entityId); _entityNodeMap[entityId] = entityNode; // Add as child of parent or as root in scene if (parentEntityId.IsValid && _entityNodeMap.TryGetValue(parentEntityId, out var parentNode)) { parentNode.Children.Add(entityNode); entityNode.ParentNode = parentNode; } else { sceneNode.Children.Add(entityNode); entityNode.ParentNode = null; } return entityNode; } /// /// Removes an entity node from the graph. /// Also removes all its children recursively. /// public bool RemoveEntity(Entity entityId) { if (!_entityNodeMap.TryGetValue(entityId, out var entityNode)) { return false; } // Remove all descendants foreach (var descendant in entityNode.GetAllDescendants()) { _entityNodeMap.Remove(descendant.EntityId); } // Remove from parent or scene if (entityNode.ParentNode != null) { entityNode.ParentNode.Children.Remove(entityNode); } else { // Find and remove from scene foreach (var sceneNode in Scenes) { if (sceneNode.Children.Contains(entityNode)) { sceneNode.Children.Remove(entityNode); break; } } } _entityNodeMap.Remove(entityId); return true; } /// /// Gets an entity node by its global entity ID. /// public EntityNode? GetEntityNode(Entity entityId) { _entityNodeMap.TryGetValue(entityId, out var entityNode); return entityNode; } /// /// Sets the parent of an entity node. /// public void SetEntityParent(Entity childEntityId, Entity newParentEntityId) { if (!_entityNodeMap.TryGetValue(childEntityId, out var childNode)) { throw new InvalidOperationException($"Entity {childEntityId} not found in scene graph."); } // Remove from current parent/scene if (childNode.ParentNode != null) { childNode.ParentNode.Children.Remove(childNode); } else { // Find and remove from scene foreach (var sceneNode in Scenes) { if (sceneNode.Children.Contains(childNode)) { sceneNode.Children.Remove(childNode); break; } } } // Add to new parent if (newParentEntityId.IsValid && _entityNodeMap.TryGetValue(newParentEntityId, out var newParentNode)) { newParentNode.Children.Add(childNode); childNode.ParentNode = newParentNode; } else { throw new InvalidOperationException($"New parent entity {newParentEntityId} not found."); } } /// /// Rebuilds the scene graph from the editor world's ECS data. /// Queries entities with SceneID and Hierarchy components. /// public void RebuildFromWorld() { Scenes.Clear(); _entityNodeMap.Clear(); _sceneNodeMap.Clear(); // TODO: Query entities with SceneID and Hierarchy components // For now, this is a placeholder that will be implemented once we have the full integration } /// /// Gets all entities in a scene. /// public IEnumerable GetEntitiesInScene(short sceneId) { var sceneNode = GetSceneNode(sceneId); if (sceneNode == null) { return Enumerable.Empty(); } var allEntities = new List(); foreach (var child in sceneNode.Children) { allEntities.Add(child); allEntities.AddRange(child.GetAllDescendants()); } return allEntities; } }