// Copyright (c) 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 FlaxEditor.Windows;
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;
if (SceneGraphFactory.Nodes.TryGetValue(id, out var duplicate) && duplicate != null)
{
Editor.LogWarning($"Duplicated Scene Graph node with ID {FlaxEngine.Json.JsonSerializer.GetStringID(id)} of type '{duplicate.GetType().FullName}'");
}
SceneGraphFactory.Nodes[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;
///
/// The list of objects to exclude from tracing against. Null if unused.
///
public List ExcludeObjects;
///
/// Initializes a new instance of the struct.
///
public RayCastData()
{
}
}
///
/// 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 (RayMask(ref ray) && 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;
}
private bool RayMask(ref RayCastData ray)
{
if (ray.ExcludeObjects != null && ray.ExcludeObjects.Remove(this))
{
// Remove form exclude because it is passed by ref and function is recursive it will slowly shrink the list until nothing is left as a micro optimization
if (ray.ExcludeObjects.Count == 0)
ray.ExcludeObjects = null;
return false;
}
return true;
}
///
/// 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);
}
}
///
/// Performs the vertex snapping for a given ray and hitDistance.
///
/// The ray to raycast.
/// Hit distance from ray to object bounding box.
/// The result point on the object mesh that is closest to the specified location.
/// True if got a valid result value, otherwise false (eg. if missing data or not initialized).
public virtual bool OnVertexSnap(ref Ray ray, Real hitDistance, out Vector3 result)
{
result = Vector3.Zero;
return false;
}
///
/// 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, EditorWindow window)
{
}
///
/// 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();
}
ChildNodes.Clear();
SceneGraphFactory.Nodes.Remove(ID);
}
///
/// Called when parent node gets changed.
///
protected virtual void OnParentChanged()
{
}
///
/// Randomizes the owner node identifier.
///
/// The owner node ID.
/// The sub-object index.
/// The sub-object ID.
protected static unsafe Guid GetSubID(Guid ownerId, int index)
{
var id = ownerId;
var idPtr = (FlaxEngine.Json.JsonSerializer.GuidInterop*)&id;
idPtr->B ^= (uint)(index * 387);
idPtr->D += (uint)(index + 1);
return id;
}
}
}