#region File Description //----------------------------------------------------------------------------- // A node is the basic container in the scene graph. Its basically a point in // transformations that can contain child nodes (and inherit transformations), // and contain renderable entities to draw inside. // // Author: Ronen Ness. // Since: 2017. //----------------------------------------------------------------------------- #endregion using Microsoft.Xna.Framework; using System.Collections.Generic; namespace MonoGameSceneGraph { /// /// A callback function you can register on different node-related events. /// /// The node instance the event came from. public delegate void NodeEventCallback(Node node); /// /// A node with transformations, you can attach renderable entities to it, or append /// child nodes to inherit transformations. /// public class Node { /// /// Parent node. /// protected Node _parent = null; /// /// Callback that triggers every time a node updates its matrix. /// public static NodeEventCallback OnTransformationsUpdate; /// /// Callback that triggers every time a node is rendered. /// Note: nodes that are culled out should not trigger this. /// public static NodeEventCallback OnDraw; /// /// Node's transformations. /// protected Transformations _transformations = new Transformations(); /// /// Is this node currently visible? /// public virtual bool Visible { get; set; } /// /// Optional identifier we can give to nodes. /// public string Identifier; /// /// Optional user data we can attach to nodes. /// public object UserData; /// /// Const return value for null bounding box. /// private static readonly BoundingBox EmptyBoundingBox = new BoundingBox(); /// /// Local transformations matrix, eg the result of the current local transformations. /// protected Matrix _localTransform = Matrix.Identity; /// /// World transformations matrix, eg the result of the local transformations multiplied with parent transformations. /// protected Matrix _worldTransform = Matrix.Identity; /// /// Child nodes under this node. /// protected List _childNodes = new List(); /// /// Child entities under this node. /// protected List _childEntities = new List(); /// /// Turns true when the transformations of this node changes. /// protected bool _isDirty = true; /// /// This number increment every time we update transformations. /// We use it to check if our parent's transformations had been changed since last /// time this node was rendered, and if so, we re-apply parent updated transformations. /// protected uint _transformVersion = 0; /// /// The last transformations version we got from our parent. /// protected uint _parentLastTransformVersion = 0; /// /// Get parent node. /// public Node Parent { get { return _parent; } } /// /// Transformation version is a special identifier that changes whenever the world transformations /// of this node changes. Its not necessarily a sequence, but if you check this number for changes every /// frame its a good indication of transformation change. /// public uint TransformVersion { get { return _transformVersion; } } /// /// Create the new node. /// public Node() { Visible = true; } /// /// Clone this scene node. /// /// Node copy. public virtual Node Clone() { Node ret = new Node(); ret._transformations = _transformations.Clone(); ret.Visible = Visible; return ret; } /// /// Draw the node and its children. /// public virtual void Draw() { // not visible? skip if (!Visible) { return; } // update transformations (only if needed, testing logic is inside) UpdateTransformations(); // draw all child nodes foreach (Node node in _childNodes) { node.Draw(); } // trigger draw event OnDraw?.Invoke(this); // draw all child entities foreach (IEntity entity in _childEntities) { entity.Draw(this, _localTransform, _worldTransform); } } /// /// Add an entity to this node. /// /// Entity to add. public void AddEntity(IEntity entity) { _childEntities.Add(entity); OnEntitiesListChange(entity, true); } /// /// Remove an entity from this node. /// /// Entity to add. public void RemoveEntity(IEntity entity) { _childEntities.Remove(entity); OnEntitiesListChange(entity, false); } /// /// Called whenever a child node was added / removed from this node. /// /// Entity that was added / removed. /// If true its an entity that was added, if false, an entity that was removed. virtual protected void OnEntitiesListChange(IEntity entity, bool wasAdded) { } /// /// Called whenever an entity was added / removed from this node. /// /// Node that was added / removed. /// If true its a node that was added, if false, a node that was removed. virtual protected void OnChildNodesListChange(Node node, bool wasAdded) { } /// /// Add a child node to this node. /// /// Node to add. public void AddChildNode(Node node) { // node already got a parent? if (node._parent != null) { throw new System.Exception("Can't add a node that already have a parent."); } // add node to children list _childNodes.Add(node); // set self as node's parent node.SetParent(this); OnChildNodesListChange(node, true); } /// /// Remove a child node from this node. /// /// Node to add. public void RemoveChildNode(Node node) { // make sure the node is a child of this node if (node._parent != this) { throw new System.Exception("Can't remove a node that don't belong to this parent."); } // remove node from children list _childNodes.Remove(node); // clear node parent node.SetParent(null); OnChildNodesListChange(node, false); } /// /// Find and return first child node by identifier. /// /// Node identifier to search for. /// If true, will also search recurisvely in children. /// Node with given identifier or null if not found. public Node FindChildNode(string identifier, bool searchInChildren = true) { foreach (Node node in _childNodes) { // search in direct children if (node.Identifier == identifier) { return node; } // recursive search if (searchInChildren) { Node foundInChild = node.FindChildNode(identifier, searchInChildren); if (foundInChild != null) { return foundInChild; } } } // if got here it means we didn't find any child node with given identifier return null; } /// /// Remove this node from its parent. /// public void RemoveFromParent() { // don't have a parent? if (_parent == null) { throw new System.Exception("Can't remove an orphan node from parent."); } // remove from parent _parent.RemoveChildNode(this); } /// /// Called when the world matrix of this node is actually recalculated (invoked after the calculation). /// protected virtual void OnWorldMatrixChange() { // update transformations version _transformVersion++; // trigger update event OnTransformationsUpdate?.Invoke(this); // notify parent if (_parent != null) { _parent.OnChildWorldMatrixChange(this); } } /// /// Called when local transformations are set, eg when Position, Rotation, Scale etc. is changed. /// We use this to set this node as "dirty", eg that we need to update local transformations. /// protected virtual void OnTransformationsSet() { _isDirty = true; } /// /// Set the parent of this node. /// /// New parent node to set, or null for no parent. protected virtual void SetParent(Node newParent) { // set parent _parent = newParent; // set our parents last transformations version to make sure we'll update world transformations next frame. _parentLastTransformVersion = newParent != null ? newParent._transformVersion - 1 : 1; } /// /// Return true if we need to update world transform due to parent change. /// private bool NeedUpdateDueToParentChange() { // no parent? if parent last transform version is not 0, it means we had a parent but now we don't. // still require update. if (_parent == null) { return _parentLastTransformVersion != 0; } // check if parent is dirty, or if our last parent transform version mismatch parent current transform version return (_parent._isDirty || _parentLastTransformVersion != _parent._transformVersion); } /// /// Calc final transformations for current frame. /// This uses an indicator to know if an update is needed, so no harm is done if you call it multiple times. /// protected virtual void UpdateTransformations() { // if local transformations are dirty, we need to update them if (_isDirty) { _localTransform = _transformations.BuildMatrix(); } // if local transformations are dirty or parent transformations are out-of-date, update world transformations if (_isDirty || NeedUpdateDueToParentChange()) { // if we got parent, apply its transformations if (_parent != null) { // if parent need update, update it first if (_parent._isDirty) { _parent.UpdateTransformations(); } // recalc world transform _worldTransform = _localTransform * _parent._worldTransform; _parentLastTransformVersion = _parent._transformVersion; } // if not, world transformations are the same as local, and reset parent last transformations version else { _worldTransform = _localTransform; _parentLastTransformVersion = 0; } // called the function that mark world matrix change (increase transformation version etc) OnWorldMatrixChange(); } // no longer dirty _isDirty = false; } /// /// Return local transformations matrix (note: will recalculate if needed). /// public Matrix LocalTransformations { get { UpdateTransformations(); return _localTransform; } } /// /// Return world transformations matrix (note: will recalculate if needed). /// public Matrix WorldTransformations { get { UpdateTransformations(); return _worldTransform; } } /// /// Get position in world space. /// /// Naive implementation using world matrix decompose. For better performance, override this with your own cached version. public virtual Vector3 WorldPosition { get { //Vector3 pos; Vector3 scale; Quaternion rot; //WorldTransformations.Decompose(out scale, out rot, out pos); return WorldTransformations.Translation; } } /// /// Get Rotastion in world space. /// /// Naive implementation using world matrix decompose. For better performance, override this with your own cached version. public virtual Quaternion WorldRotation { get { Vector3 pos; Vector3 scale; Quaternion rot; WorldTransformations.Decompose(out scale, out rot, out pos); return rot; } } /// /// Get Scale in world space. /// /// Naive implementation using world matrix decompose. For better performance, override this with your own cached version. public virtual Vector3 WorldScale { get { Vector3 pos; Vector3 scale; Quaternion rot; WorldTransformations.Decompose(out scale, out rot, out pos); return scale; } } /// /// Force update transformations for this node and its children. /// /// If true, will also iterate and force-update children. public void ForceUpdate(bool recursive = true) { // not visible? skip if (!Visible) { return; } // update transformations (only if needed, testing logic is inside) UpdateTransformations(); // force-update all child nodes if (recursive) { foreach (Node node in _childNodes) { node.ForceUpdate(recursive); } } } /// /// Reset all local transformations. /// public void ResetTransformations() { _transformations = new Transformations(); OnTransformationsSet(); } /// /// Get / Set the order in which we apply local transformations in this node. /// public TransformOrder TransformationsOrder { get { return _transformations.TransformOrder; } set { _transformations.TransformOrder = value; OnTransformationsSet(); } } /// /// Get / Set the rotation type (euler / quaternion). /// public RotationType RotationType { get { return _transformations.RotationType; } set { _transformations.RotationType = value; OnTransformationsSet(); } } /// /// Get / Set the order in which we apply local rotation in this node. /// public RotationOrder RotationOrder { get { return _transformations.RotationOrder; } set { _transformations.RotationOrder = value; OnTransformationsSet(); } } /// /// Get / Set node local position. /// public Vector3 Position { get { return _transformations.Position; } set { if (_transformations.Position != value) OnTransformationsSet(); _transformations.Position = value; } } /// /// Get / Set node local scale. /// public Vector3 Scale { get { return _transformations.Scale; } set { if (_transformations.Scale != value) OnTransformationsSet(); _transformations.Scale = value; } } /// /// Get / Set node local rotation. /// public Vector3 Rotation { get { return _transformations.Rotation; } set { if (_transformations.Rotation != value) OnTransformationsSet(); _transformations.Rotation = value; } } /// /// Alias to access rotation X directly. /// public float RotationX { get { return _transformations.Rotation.X; } set { if (_transformations.Rotation.X != value) OnTransformationsSet(); _transformations.Rotation.X = value; } } /// /// Alias to access rotation Y directly. /// public float RotationY { get { return _transformations.Rotation.Y; } set { if (_transformations.Rotation.Y != value) OnTransformationsSet(); _transformations.Rotation.Y = value; } } /// /// Alias to access rotation Z directly. /// public float RotationZ { get { return _transformations.Rotation.Z; } set { if (_transformations.Rotation.Z != value) OnTransformationsSet(); _transformations.Rotation.Z = value; } } /// /// Alias to access scale X directly. /// public float ScaleX { get { return _transformations.Scale.X; } set { if (_transformations.Scale.X != value) OnTransformationsSet(); _transformations.Scale.X = value; } } /// /// Alias to access scale Y directly. /// public float ScaleY { get { return _transformations.Scale.Y; } set { if (_transformations.Scale.Y != value) OnTransformationsSet(); _transformations.Scale.Y = value; } } /// /// Alias to access scale Z directly. /// public float ScaleZ { get { return _transformations.Scale.Z; } set { if (_transformations.Scale.Z != value) OnTransformationsSet(); _transformations.Scale.Z = value; } } /// /// Alias to access position X directly. /// public float PositionX { get { return _transformations.Position.X; } set { if (_transformations.Position.X != value) OnTransformationsSet(); _transformations.Position.X = value; } } /// /// Alias to access position Y directly. /// public float PositionY { get { return _transformations.Position.Y; } set { if (_transformations.Position.Y != value) OnTransformationsSet(); _transformations.Position.Y = value; } } /// /// Alias to access position Z directly. /// public float PositionZ { get { return _transformations.Position.Z; } set { if (_transformations.Position.Z != value) OnTransformationsSet(); _transformations.Position.Z = value; } } /// /// Move position by vector. /// /// Vector to translate by. public void Translate(Vector3 moveBy) { _transformations.Position += moveBy; OnTransformationsSet(); } /// /// Called every time one of the child nodes recalculate world transformations. /// /// The child node that updated. public virtual void OnChildWorldMatrixChange(Node node) { } /// /// Return true if this node is empty. /// public bool Empty { get { return _childEntities.Count == 0 && _childNodes.Count == 0; } } /// /// Get if this node have any entities in it. /// public bool HaveEntities { get { return _childEntities.Count != 0; } } /// /// Get bounding box of this node and all its child nodes. /// /// If true, will include bounding box of child nodes. If false, only of entities directly attached to this node. /// Bounding box of the node and its children. public virtual BoundingBox GetBoundingBox(bool includeChildNodes = true) { // if empty skip if (Empty) { return EmptyBoundingBox; } // make sure transformations are up-to-date UpdateTransformations(); // list of points to build bounding box from List corners = new List(); // apply all child nodes bounding boxes if (includeChildNodes) { foreach (Node child in _childNodes) { // skip invisible nodes if (!child.Visible) { continue; } // get bounding box BoundingBox currBox = child.GetBoundingBox(); if (currBox.Min != currBox.Max) { corners.Add(currBox.Min); corners.Add(currBox.Max); } } } // apply all entities directly under this node foreach (IEntity entity in _childEntities) { // skip invisible entities if (!entity.Visible) { continue; } // get entity bounding box BoundingBox currBox = entity.GetBoundingBox(this, _localTransform, _worldTransform); if (currBox.Min != currBox.Max) { corners.Add(currBox.Min); corners.Add(currBox.Max); } } // nothing in this node? if (corners.Count == 0) { return EmptyBoundingBox; } // return final bounding box return BoundingBox.CreateFromPoints(corners); } } }