// 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 System.Linq; using FlaxEditor.Modules; using FlaxEditor.SceneGraph.Actors; using FlaxEngine; namespace FlaxEditor.SceneGraph { /// /// Base class for all leaf node objects which belong to scene graph used by the Editor. /// Scene Graph is directional graph without cyclic references. It's a tree. /// A class is responsible for Scene Graph management. /// [HideInEditor] public abstract class SceneGraphNode { /// /// The parent node. /// protected SceneGraphNode parentNode; /// /// Gets the children list. /// public List ChildNodes { get; } = new List(); /// /// Initializes a new instance of the class. /// /// The unique node identifier. Cannot be changed at runtime. protected SceneGraphNode(Guid id) { ID = id; SceneGraphFactory.Nodes.Add(id, this); } /// /// Gets the name. /// public abstract string Name { get; } /// /// Gets the identifier. Must be unique and immutable. /// public Guid ID { get; } /// /// Gets the parent scene. /// public abstract SceneNode ParentScene { get; } /// /// Gets the root node of the scene graph (if has). /// public virtual RootNode Root => ParentNode?.Root; /// /// Gets or sets the transform of the node. /// public abstract Transform Transform { get; set; } /// /// Gets a value indicating whether this instance can be copied or/and pasted. /// public virtual bool CanCopyPaste => true; /// /// Gets a value indicating whether this instance can be duplicated by the user. /// public virtual bool CanDuplicate => true; /// /// Gets a value indicating whether this node can be deleted by the user. /// public virtual bool CanDelete => true; /// /// Gets a value indicating whether this node can be dragged by the user. /// public virtual bool CanDrag => true; /// /// Gets a value indicating whether this node can be transformed by the user. /// public virtual bool CanTransform => true; /// /// Gets a value indicating whether this is active. /// public abstract bool IsActive { get; } /// /// Gets a value indicating whether this is active and all parent nodes are also active. /// public abstract bool IsActiveInHierarchy { get; } /// /// Gets or sets order of the node in the parent container. /// public abstract int OrderInParent { get; set; } /// /// Gets or sets the parent node. /// public virtual SceneGraphNode ParentNode { get => parentNode; set { if (parentNode != value) { parentNode?.ChildNodes.Remove(this); parentNode = value; parentNode?.ChildNodes.Add(this); OnParentChanged(); } } } /// /// Gets the object to edit via properties editor when this node is being selected. /// public virtual object EditableObject => this; /// /// Gets the object used to record undo changes. /// public virtual object UndoRecordObject => EditableObject; /// /// Determines whether the specified object is in a hierarchy (one of the children or lower). /// /// The node to check, /// True if given actor is part of the hierarchy, otherwise false. public virtual bool ContainsInHierarchy(SceneGraphNode node) { if (ChildNodes.Contains(node)) return true; return ChildNodes.Any(x => x.ContainsInHierarchy(node)); } /// /// Determines whether the specified object is one of the children. /// /// The node to check, /// True if given object is a child, otherwise false. public virtual bool ContainsChild(SceneGraphNode node) { return ChildNodes.Contains(node); } /// /// Adds the child node. /// /// The node. public void AddChild(SceneGraphNode node) { node.ParentNode = this; } /// /// The scene graph raycasting data container. /// [HideInEditor] public struct RayCastData { /// /// The raycasting optional flags. /// [Flags, HideInEditor] public enum FlagTypes { /// /// The none. /// None = 0, /// /// The skip colliders flag. Use it to ignore physics colliders intersections detection. /// SkipColliders = 1, /// /// The skip editor primitives. Use it to ignore editor icons and primitives intersections detection. /// SkipEditorPrimitives = 2, /// /// The skip trigger colliders flag. Use it to ignore physics trigger colliders intersections detection. /// SkipTriggers = 4, } /// /// The ray (for intersection raycasting). /// public Ray Ray; /// /// The camera view ray (camera position and direction). /// public Ray View; /// /// The flags. /// public FlagTypes Flags; } /// /// Performs raycasting over child nodes hierarchy trying to get the closest object hit by the given ray. /// /// The ray casting data. /// The result distance. /// The result intersection surface normal vector. /// Hit object or null if there is no intersection at all. public virtual SceneGraphNode RayCastChildren(ref RayCastData ray, out Real distance, out Vector3 normal) { if (!IsActive) { distance = 0; normal = Vector3.Up; return null; } SceneGraphNode minTarget = null; Real minDistance = Real.MaxValue; Vector3 minDistanceNormal = Vector3.Up; // Check all children for (int i = 0; i < ChildNodes.Count; i++) { var hit = ChildNodes[i].RayCast(ref ray, out distance, out normal); if (hit != null && distance <= minDistance) { minTarget = hit; minDistance = distance; minDistanceNormal = normal; } } // Return result distance = minDistance; normal = minDistanceNormal; return minTarget; } /// /// Performs raycasting over nodes hierarchy trying to get the closest object hit by the given ray. /// /// The ray casting data. /// The result distance. /// The result intersection surface normal vector. /// Hit object or null if there is no intersection at all. public virtual SceneGraphNode RayCast(ref RayCastData ray, out Real distance, out Vector3 normal) { if (!IsActive) { distance = 0; normal = Vector3.Up; return null; } // Check itself SceneGraphNode minTarget = null; Real minDistance = Real.MaxValue; Vector3 minDistanceNormal = Vector3.Up; if (RayCastSelf(ref ray, out distance, out normal)) { minTarget = this; minDistance = distance; minDistanceNormal = normal; } // Check all children for (int i = 0; i < ChildNodes.Count; i++) { var hit = ChildNodes[i].RayCast(ref ray, out distance, out normal); if (hit != null && distance <= minDistance) { minTarget = hit; minDistance = distance; minDistanceNormal = normal; } } // Return result distance = minDistance; normal = minDistanceNormal; return minTarget; } /// /// Checks if given ray intersects with the node. /// /// The ray casting data. /// The distance. /// The result intersection surface normal vector. /// True ray hits this node, otherwise false. public virtual bool RayCastSelf(ref RayCastData ray, out Real distance, out Vector3 normal) { distance = 0; normal = Vector3.Up; return false; } /// /// Gets the object bounding sphere (including child actors). /// /// The bounding sphere. public virtual void GetEditorSphere(out BoundingSphere sphere) { sphere = new BoundingSphere(Transform.Translation, 15.0f); for (int i = 0; i < ChildNodes.Count; i++) { ChildNodes[i].GetEditorSphere(out var childSphere); BoundingSphere.Merge(ref sphere, ref childSphere, out sphere); } } /// /// Called when selected nodes should draw debug shapes using interface. /// /// The debug draw data. public virtual void OnDebugDraw(ViewportDebugDrawData data) { } /// /// Called when scene tree window wants to show the context menu. Allows to add custom options. /// public virtual void OnContextMenu(FlaxEditor.GUI.ContextMenu.ContextMenu contextMenu) { } /// /// The scene graph node state container. Used for Editor undo actions (eg. restoring deleted node). /// [HideInEditor] public struct StateData { /// /// The name of the scene graph node type (full). /// public string TypeName; /// /// The name of the method (in ) that takes this state as a parameter and returns the created scene graph node. Used by the undo actions to restore deleted objects. /// public string CreateMethodName; /// /// The custom state data (as string). /// public string State; /// /// The custom state data (as raw bytes). /// public byte[] StateRaw; } /// /// Gets a value indicating whether this node can use property for editor undo operations. /// public virtual bool CanUseState => false; /// /// Gets or sets the node state. /// [NoSerialize] public virtual StateData State { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } /// /// Deletes object represented by this node eg. actor. /// public virtual void Delete() { } /// /// Duplicates this object. Valid only if returns true. /// /// The undo action that duplicated the object (already performed), null if skip it. /// The duplicated object node. public virtual SceneGraphNode Duplicate(out IUndoAction undoAction) { undoAction = null; return null; } /// /// Releases the node and the child tree. Disposed all GUI parts and used resources. /// public virtual void Dispose() { OnDispose(); // Unlink from the parent if (parentNode != null) { parentNode.ChildNodes.Remove(this); parentNode = null; } } /// /// Called when node or parent node is disposing. /// public virtual void OnDispose() { // Call deeper for (int i = 0; i < ChildNodes.Count; i++) { ChildNodes[i].OnDispose(); } SceneGraphFactory.Nodes.Remove(ID); } /// /// Called when parent node gets changed. /// protected virtual void OnParentChanged() { } } }