#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 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; /// /// 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(); /// /// The order in which we apply transformations when building the matrix for this node. /// protected TransformOrder _transformationsOrder = TransformOrder.ScaleRotationPosition; /// /// The order in which we apply rotation when building the matrix for this node. /// protected RotationOrder _rotationOrder = RotationOrder.RotateYXZ; /// /// 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; } /// /// 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(); } // 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); } /// /// Remove an entity from this node. /// /// Entity to add. public void RemoveEntity(IEntity entity) { _childEntities.Remove(entity); } /// /// 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); } /// /// 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); } /// /// 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++; // 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; } /// /// 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(_transformationsOrder, _rotationOrder); } // if local transformations are dirty, or parent transformations are out-of-date, update world transformations if (_isDirty || (_parent != null && _parentLastTransformVersion != _parent._transformVersion) || (_parent == null && _parentLastTransformVersion != 0)) { // if we got parent, apply its transformations if (_parent != null) { _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; } } /// /// 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 _transformationsOrder; } set { _transformationsOrder = value; OnTransformationsSet(); } } /// /// Get / Set the order in which we apply local rotation in this node. /// public RotationOrder RotationOrder { get { return _rotationOrder; } set { _rotationOrder = value; OnTransformationsSet(); } } /// /// Get / Set node local position. /// public Vector3 Position { get { return _transformations.Position; } set { _transformations.Position = value; OnTransformationsSet(); } } /// /// Get / Set node local scale. /// public Vector3 Scale { get { return _transformations.Scale; } set { _transformations.Scale = value; OnTransformationsSet(); } } /// /// Get / Set node local rotation. /// public Vector3 Rotation { get { return _transformations.Rotation; } set { _transformations.Rotation = value; OnTransformationsSet(); } } /// /// Alias to access rotation X directly. /// public float RotationX { get { return _transformations.Rotation.X; } set { _transformations.Rotation.X = value; OnTransformationsSet(); } } /// /// Alias to access rotation Y directly. /// public float RotationY { get { return _transformations.Rotation.Y; } set { _transformations.Rotation.Y = value; OnTransformationsSet(); } } /// /// Alias to access rotation Z directly. /// public float RotationZ { get { return _transformations.Rotation.Z; } set { _transformations.Rotation.Z = value; OnTransformationsSet(); } } /// /// Alias to access scale X directly. /// public float ScaleX { get { return _transformations.Scale.X; } set { _transformations.Scale.X = value; OnTransformationsSet(); } } /// /// Alias to access scale Y directly. /// public float ScaleY { get { return _transformations.Scale.Y; } set { _transformations.Scale.Y = value; OnTransformationsSet(); } } /// /// Alias to access scale Z directly. /// public float ScaleZ { get { return _transformations.Scale.Z; } set { _transformations.Scale.Z = value; OnTransformationsSet(); } } /// /// Alias to access position X directly. /// public float PositionX { get { return _transformations.Position.X; } set { _transformations.Position.X = value; OnTransformationsSet(); } } /// /// Alias to access position Y directly. /// public float PositionY { get { return _transformations.Position.Y; } set { _transformations.Position.Y = value; OnTransformationsSet(); } } /// /// Alias to access position Z directly. /// public float PositionZ { get { return _transformations.Position.Z; } set { _transformations.Position.Z = value; OnTransformationsSet(); } } /// /// 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 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); } } }