#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 { /// /// MonoGameSceneGraph is the main namespace that contains all the MonoGame-SceneGraph entities. /// [System.Runtime.CompilerServices.CompilerGenerated] class NamespaceDoc { } /// /// 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 bool IsVisible = true; /// /// Optional identifier we can give to nodes. /// public string Identifier; /// /// Optional user data we can attach to nodes. /// public object UserData; /// /// 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; } } /// /// Draw the node and its children. /// public virtual void Draw() { // not visible? skip if (!IsVisible) { 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); } /// /// Set this node as "dirty", eg that we need to update local transformations. /// protected virtual void OnWorldMatrixChange() { _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; } // increase transformation version _transformVersion++; } // 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(); OnWorldMatrixChange(); } /// /// Get / Set the order in which we apply local transformations in this node. /// public TransformOrder TransformationsOrder { get { return _transformationsOrder; } set { _transformationsOrder = value; OnWorldMatrixChange(); } } /// /// Get / Set the order in which we apply local rotation in this node. /// public RotationOrder RotationOrder { get { return _rotationOrder; } set { _rotationOrder = value; OnWorldMatrixChange(); } } /// /// Get / Set node local position. /// public Vector3 Position { get { return _transformations.Position; } set { _transformations.Position = value; OnWorldMatrixChange(); } } /// /// Get / Set node local scale. /// public Vector3 Scale { get { return _transformations.Scale; } set { _transformations.Scale = value; OnWorldMatrixChange(); } } /// /// Get / Set node local rotation. /// public Vector3 Rotation { get { return _transformations.Rotation; } set { _transformations.Rotation = value; OnWorldMatrixChange(); } } /// /// Alias to access rotation X directly. /// public float RotationX { get { return _transformations.Rotation.X; } set { _transformations.Rotation.X = value; OnWorldMatrixChange(); } } /// /// Alias to access rotation Y directly. /// public float RotationY { get { return _transformations.Rotation.Y; } set { _transformations.Rotation.Y = value; OnWorldMatrixChange(); } } /// /// Alias to access rotation Z directly. /// public float RotationZ { get { return _transformations.Rotation.Z; } set { _transformations.Rotation.Z = value; OnWorldMatrixChange(); } } /// /// Alias to access scale X directly. /// public float ScaleX { get { return _transformations.Scale.X; } set { _transformations.Scale.X = value; OnWorldMatrixChange(); } } /// /// Alias to access scale Y directly. /// public float ScaleY { get { return _transformations.Scale.Y; } set { _transformations.Scale.Y = value; OnWorldMatrixChange(); } } /// /// Alias to access scale Z directly. /// public float ScaleZ { get { return _transformations.Scale.Z; } set { _transformations.Scale.Z = value; OnWorldMatrixChange(); } } /// /// Alias to access position X directly. /// public float PositionX { get { return _transformations.Position.X; } set { _transformations.Position.X = value; OnWorldMatrixChange(); } } /// /// Alias to access position Y directly. /// public float PositionY { get { return _transformations.Position.Y; } set { _transformations.Position.Y = value; OnWorldMatrixChange(); } } /// /// Alias to access position Z directly. /// public float PositionZ { get { return _transformations.Position.Z; } set { _transformations.Position.Z = value; OnWorldMatrixChange(); } } /// /// Move position by vector. /// /// Vector to translate by. public void Translate(Vector3 moveBy) { _transformations.Position += moveBy; OnWorldMatrixChange(); } } }