// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #if USE_LARGE_WORLDS using Real = System.Double; #else using Real = System.Single; #endif using System; using System.Collections.Generic; using FlaxEditor.SceneGraph.Actors; using FlaxEditor.SceneGraph.GUI; using FlaxEditor.Windows; using FlaxEngine; namespace FlaxEditor.SceneGraph { /// /// A tree node used to visualize scene actors structure in . It's a ViewModel object for . /// It's part of the Scene Graph. /// /// /// [HideInEditor] public class ActorNode : SceneGraphNode { /// /// The linked actor object. /// protected readonly Actor _actor; /// /// The tree node used to present hierarchy structure in GUI. /// protected readonly ActorTreeNode _treeNode; /// /// Gets the actor. /// public Actor Actor => _actor; /// /// Gets the tree node (part of the GUI). /// public ActorTreeNode TreeNode => _treeNode; /// /// The actor child nodes used to represent special parts of the actor (meshes, links, surfaces). /// public List ActorChildNodes; /// /// Initializes a new instance of the class. /// /// The actor. public ActorNode(Actor actor) : base(actor.ID) { _actor = actor; _treeNode = new ActorTreeNode(); _treeNode.LinkNode(this); } /// /// Initializes a new instance of the class. /// /// The actor. /// The custom tree node. protected ActorNode(Actor actor, ActorTreeNode treeNode) : base(actor.ID) { _actor = actor; _treeNode = treeNode; _treeNode.LinkNode(this); } internal ActorNode(Actor actor, Guid id) : base(id) { _actor = actor; _treeNode = new ActorTreeNode(); _treeNode.LinkNode(this); } /// /// Gets a value indicating whether this actor affects navigation. /// public virtual bool AffectsNavigation => false; /// /// Gets a value indicating whether this actor affects navigation or any of its children (recursive). /// public bool AffectsNavigationWithChildren { get { if (_actor.HasStaticFlag(StaticFlags.Navigation) && AffectsNavigation) return true; for (var i = 0; i < ChildNodes.Count; i++) { if (ChildNodes[i] is ActorNode actorChild && actorChild.AffectsNavigationWithChildren) return true; } return false; } } /// /// Tries to find the tree node for the specified actor. /// /// The actor. /// Tree node or null if cannot find it. public ActorNode Find(Actor actor) { // Check itself if (_actor == actor) return this; // Check deeper for (int i = 0; i < ChildNodes.Count; i++) { if (ChildNodes[i] is ActorNode node) { var result = node.Find(actor); if (result != null) return result; } } return null; } /// /// Adds the child node. /// /// The node. /// The node public ActorChildNode AddChildNode(ActorChildNode node) { if (ActorChildNodes == null) ActorChildNodes = new List(); ActorChildNodes.Add(node); node.ParentNode = this; return node; } /// /// Disposes the child nodes. /// public void DisposeChildNodes() { // Send event to root so if any of this child nodes is selected we can handle it var root = Root; if (root != null) { root.OnActorChildNodesDispose(this); } if (ActorChildNodes != null) { for (int i = 0; i < ActorChildNodes.Count; i++) ActorChildNodes[i].Dispose(); ActorChildNodes.Clear(); } } /// /// Tries to find the tree node for the specified actor in child nodes collection. /// /// The actor. /// Tree node or null if cannot find it. public ActorNode FindChildActor(Actor actor) { for (int i = 0; i < ChildNodes.Count; i++) { if (ChildNodes[i] is ActorNode node && node.Actor == actor) { return node; } } return null; } /// /// Gets a value indicating whether this actor can be used to create prefab from it (as a root). /// public virtual bool CanCreatePrefab => (_actor.HideFlags & HideFlags.DontSave) != HideFlags.DontSave; /// /// Gets a value indicating whether this actor has a valid linkage to the prefab asset. /// public virtual bool HasPrefabLink => _actor.HasPrefabLink; /// public override string Name => _actor.Name; /// public override SceneNode ParentScene { get { var scene = _actor ? _actor.Scene : null; return scene != null ? SceneGraphFactory.FindNode(scene.ID) as SceneNode : null; } } /// public override bool CanTransform => (_actor.StaticFlags & StaticFlags.Transform) == 0; /// public override bool CanCopyPaste => (_actor.HideFlags & HideFlags.HideInHierarchy) == 0; /// public override bool CanDuplicate => (_actor.HideFlags & HideFlags.HideInHierarchy) == 0; /// public override bool IsActive => _actor.IsActive; /// public override bool IsActiveInHierarchy => _actor.IsActiveInHierarchy; /// public override int OrderInParent { get => _actor.OrderInParent; set => _actor.OrderInParent = value; } /// public override Transform Transform { get => _actor.Transform; set => _actor.Transform = value; } #if false /// public override SceneGraphNode ParentNode { set { if (!(value is ActorNode)) throw new InvalidOperationException("ActorNode can have only ActorNode as a parent node."); base.ParentNode = value; } } #endif /// public override object EditableObject => _actor; /// public override SceneGraphNode RayCast(ref RayCastData ray, out Real distance, out Vector3 normal) { var hit = base.RayCast(ref ray, out distance, out normal); // Skip actors that should not be selected if (hit != null && _actor != null && (_actor.HideFlags & HideFlags.DontSelect) == HideFlags.DontSelect) { hit = parentNode; } return hit; } /// public override bool RayCastSelf(ref RayCastData ray, out Real distance, out Vector3 normal) { return _actor.IntersectsItself(ray.Ray, out distance, out normal); } /// public override void GetEditorSphere(out BoundingSphere sphere) { Editor.GetActorEditorSphere(_actor, out sphere); } /// public override void OnDebugDraw(ViewportDebugDrawData data) { data.Add(_actor); } /// public override void Delete() { FlaxEngine.Object.Destroy(_actor); } /// /// Action called after spawning actor in editor (via drag to viewport, with toolbox, etc.). /// Can be used to tweak default values of the actor. /// public virtual void PostSpawn() { } /// /// Action called after pasting actor in editor. /// public virtual void PostPaste() { } /// /// Action called after converting actor in editor. /// /// The source actor node from which this node was converted. public virtual void PostConvert(ActorNode source) { } /// protected override void OnParentChanged() { base.OnParentChanged(); // Update UI (special case if actor is spawned and added to existing scene tree) var parentTreeNode = (parentNode as ActorNode)?.TreeNode; if (parentTreeNode != null && !parentTreeNode.IsLayoutLocked) { parentTreeNode.IsLayoutLocked = true; _treeNode.Parent = parentTreeNode; _treeNode.IndexInParent = _actor.OrderInParent; parentTreeNode.IsLayoutLocked = false; // Skip UI update if node won't be in a view if (parentTreeNode.IsCollapsed) { TreeNode.UnlockChildrenRecursive(); } else { // Try to perform layout at the level where it makes it the most performant (the least computations) var tree = parentTreeNode.ParentTree; if (tree != null) { if (tree.Parent is FlaxEngine.GUI.Panel treeParent) treeParent.PerformLayout(); else tree.PerformLayout(); } else { parentTreeNode.PerformLayout(); } } } else { _treeNode.Parent = parentTreeNode; } } /// public override void Dispose() { _treeNode.Dispose(); if (ActorChildNodes != null) { ActorChildNodes.Clear(); ActorChildNodes = null; } base.Dispose(); } /// public override string ToString() { return _actor ? _actor.ToString() : base.ToString(); } } }