using System; using System.Collections.Generic; using Chernobyl.DesignPatterns.Extension; using Chernobyl.Mathematics.Vectors; using Chernobyl.Measures; using Chernobyl.Values; namespace Chernobyl.Mathematics.Movement { /// /// A helper class to make it easier to implement ITransforms. You should /// never 'handle' the type because not all /// transforms derive from . Instead, work with /// . /// public abstract class Transform : Extension, ITransform { /// /// Constructor that creates a new Transform instance, sets the /// equal to the identity matrix, and makes the /// list a /// instance. /// protected Transform() : this(null) {} /// /// Constructor that creates a new Transform instance and sets the /// equal to the identity matrix. /// /// The list to use for the /// list or null if the default /// should be used. protected Transform(IList childList) { TransformChildren = childList ?? new List(); Extensions = new List(); } /// /// Translates the transform in the X axis by a specified amount. /// /// The amount to translate in the X. public abstract void TranslateX(float x); /// /// Translates the transform in the Y axis by a specified amount. /// /// The amount to translate in the Y. public abstract void TranslateY(float y); /// /// Translates the transform in the Z axis by a specified amount. /// /// The amount to translate in the Z. public abstract void TranslateZ(float z); /// /// Translates the transform in the X and Y axis by a specified amount. /// This method is useful for transforms that exist in 2D world. /// /// The amount to translate in the X. /// The amount to translate in the Y. public abstract void Translate(float x, float y); /// /// Translates the transform in the X and Y axis by a specified amount. /// This method is useful for transforms that exist in 2D world. /// /// The amount to translate in the X and Y axis. public void Translate(Vector2 amount) { Translate(amount.X, amount.Y); } /// /// Translates the transform in the X axis, Y axis /// and Z axis by specified amounts. /// /// The amount to translate in the X. /// The amount to translate in the Y. /// The amount to translate in the Z. public abstract void Translate(float x, float y, float z); /// /// Translates the transform in the X axis, Y axis and Z axis by /// specified amounts. /// /// The amount to translate the transform in each /// of the three axis where Vector3.X is the X axis translation amount, /// Vector3.Y is the Y axis translation amount, and Vector3.Z is the /// Z axis translation amount. public void Translate(Vector3 amount) { Translate(amount.X, amount.Y, amount.Z); } /// /// Rotates this transform around a vector by a specified amount. /// /// The vector to rotate around. /// The amount to rotate around the vector. public abstract void Rotate(Vector3 vectorToRotateAround, Radian radian); /// /// Rotates this transform about the X axis a specified amount. /// /// The amount to rotate by. public abstract void Pitch(Radian amount); /// /// Rotates this transform about the Y axis a specified amount. /// /// The amount to rotate by. public abstract void Yaw(Radian amount); /// /// Rotates this transform about the Z axis a specified amount. /// /// The amount to rotate by. public abstract void Roll(Radian amount); /// /// Scales the transform along the X axis. /// /// The amount to scale in the X axis. public abstract void ScaleX(float amount); /// /// Scales the transform in the Y axis. /// /// The amount to scale in the Y axis. public abstract void ScaleY(float amount); /// /// Scales the transform in the Z axis. /// /// The amount to scale in the Z axis. public abstract void ScaleZ(float amount); /// /// Scales the transform uniformly over the X, Y, and Z axis. /// /// The amount to scale in all axis. public void Scale(float amount) { Scale(amount, amount, amount); } /// /// Scales the transform in the X and Y axis. This is useful if the /// transform exists in a 2D world. /// /// The amount to scale in the X axis. /// The amount to scale in the Y axis. public abstract void Scale(float x, float y); /// /// Scales the transform in the X and Y axis. This is useful if the /// transform exists in a 2D world. /// /// The amount to scale in the X and Y axis. public void Scale(Vector2 amount) { Scale(amount.X, amount.Y); } /// /// Scales the transform uniformly over the X, Y, and Z axis. /// /// The amount to scale in the X axis. /// The amount to scale in the Y axis. /// The amount to scale in the Z axis. public abstract void Scale(float x, float y, float z); /// /// Scales the transform uniformly over the X, Y, and Z axis. /// /// The amount to scale in each axis where /// Vector3.X is the amount to scale in the X axis, Vector3.Y is the /// amount to scale in the Y axis, Vector3.Z is the amount to scale in /// the Z axis. public void Scale(Vector3 amount) { Scale(amount.X, amount.Y, amount.Z); } /// /// Sets the internal local transformation to the matrix passed in. /// /// The matrix to set the internal local /// transformation to. public abstract void SetTransformation(ref Matrix4 matrix); /// /// Sets the needed flag to true. /// /// The transform to make a parent of this /// transform. protected override void AttachTo(ITransform parent) { IsUpdateNeeded = true; } /// /// Sets the needed flag to true. /// /// The transform to separate from this transform. protected override void DetachFrom(ITransform parent) { IsUpdateNeeded = true; } /// /// An event that is raised when the transform has been "dirtied" or /// when the position, orientation, and/or scale of the transform has /// been changed either due to an ancestor being changed or due to the /// transform itself being changed. This event will only be raised once /// when the transform is changed, subsequent changes to the transform /// or it's ancestors will not cause the event to be raised. If the /// transform is cleaned (typically done when the /// is accessed) and then dirtied again, the event will be raised. /// public event EventHandler TransformDirtied; /// /// The parent to this transform. This property will be null if this is /// the root of the hierarchy. /// public ITransform TransformParent { get { return Extended; } set { Extended = value; } } /// /// The children of this transform who's position, orientation, scale, /// etc., are effected or offset by this transform. /// public IList TransformChildren { get; protected set; } /// /// This transforms position in the world. This property returns the /// World position (i.e. the position from ) but /// sets the Local position (i.e. the position from . /// public virtual Vector3 Position { get { return WorldMatrix.Translation; } set { Matrix4 local = LocalMatrix; local.Row3 = new Vector4(value.X, value.Y, value.Z, local.Row3.W); SetTransformation(ref local); } } /// /// The up vector of this transform in the world, which is also the +Y /// vector or . /// public virtual Vector3 Up { get { return WorldMatrix.Up; } } /// /// The vector that points straight ahead (in the world) for this /// transform, which is also the -Z vector or negative /// . /// public virtual Vector3 Forward { get { return WorldMatrix.Forward; } } /// /// The vector that points to this transform's right in the world, which /// is also the +X vector or . /// public virtual Vector3 Right { get { return WorldMatrix.Right; } } /// /// The amount of scale in the world X axis of the transform. /// public float XScale { get { return WorldMatrix.Column0.Xyz.Length; } } /// /// The amount of scale in the world Y axis of the transform. /// public float YScale { get { return WorldMatrix.Column1.Xyz.Length; } } /// /// The amount of scale in the world Z axis of the transform. /// public float ZScale { get { return WorldMatrix.Column2.Xyz.Length; } } /// /// Gets/sets this transforms local matrix. The local matrix /// is the matrix that has been "untouched" by the parents, /// grandparents, etc., of this transform. In other words, /// it is not relative to any other transform. /// public abstract Matrix4 LocalMatrix { get; protected set; } /// /// Gets/sets this transform's world matrix. The world matrix /// is the position and orientation relative to the parent of /// this transform. /// public abstract Matrix4 WorldMatrix { get; protected set; } /// /// Orders this transform so that it's world matrix represents it's /// local matrix relative to it's parent's world matrix. /// protected virtual void Update() { if (IsUpdateNeeded == true) { PreUpdate(new Values.LazyValue(() => { Matrix4 world; CalculateWorldMatrix(out world); return world; })); Matrix4 worldMatrix; CalculateWorldMatrix(out worldMatrix); WorldMatrix = worldMatrix; IsUpdateNeeded = false; // we are "clean" now.. PostUpdate(ref worldMatrix); } } /// /// This method is invoked during the update of the /// right before the /// is actually updated. Typically /// you would override this method if you would like to modify the /// prior to the final calculation /// of the . /// /// The value of the . /// It is stored in a so that the calculation can be /// avoided if necessary. Don't call unless /// required. protected virtual void PreUpdate(Values.LazyValue world) { /* no-op */ } /// /// This method is invoked during the update of the /// right after the /// has been updated. Typically you /// would override this method if you would like to update some other /// data based on the new value of the whose /// value is passed to this method. /// /// The new value of the /// . protected virtual void PostUpdate(ref Matrix4 world) { /* no-op */ } /// /// A helper method for derived types that calculates the current world /// matrix of this . /// /// The that is to take the /// calculation of the world matrix. void CalculateWorldMatrix(out Matrix4 result) { // if we have a parent: recompute our world matrix using our // local matrix and the world matrix of our parent if (TransformParent != null) { // In some cases, the parent may actively modify the children // transforms. This update may occur when the WorldMatrix of the // parent is accessed. If that is the case then we need to access // the parent's WorldMatrix before we access this instances // LocalMatrix so that the parent has a chance to modify the // child. Matrix4 parentWorld = TransformParent.WorldMatrix; result = LocalMatrix * parentWorld; } else // if we don't have a parent: our local is our world result = LocalMatrix; } /// /// True if an update to this transform's world matrix is /// needed, false if otherwise. A transform will need to be /// updated if it's local matrix or an ancestors local matrix /// is updated. /// public virtual bool IsUpdateNeeded { get { return _isUpdateNeeded; } set { bool previousValue = _isUpdateNeeded; _isUpdateNeeded = value; // only tell our children and subscribers to the TransformDirtied // event if an update is needed and if they don't already know if (value == true && previousValue != true) { if (TransformDirtied != null) TransformDirtied(this, EventArgs.Empty); // warn our children about this foreach (ITransform child in TransformChildren) child.IsUpdateNeeded = true; } } } /// public void Dispose() => SeparateParentChild(TransformParent, this); /// /// A helper method that takes one transform and makes it the parent of /// another. When this is done, the parent will "affect" or offset the /// transform of the child. If a previous parent is attached to child, /// that parent will first be separated from the child passed in. /// /// The transform that is to become the parent of /// the child. /// The transform that is to become the child of the /// parent. public static void MakeParentChild(ITransform parent, ITransform child) { // make sure the child isn't already a child of the parent if (parent.TransformChildren.Contains(child) == false) { parent.TransformChildren.Add(child); // if the child has a parent, have it detach from that parent if (child.TransformParent != null) child.TransformParent.TransformChildren.Remove(child); child.TransformParent = parent; } } /// /// A helper method that splits apart a parent/child relationship of two /// transforms. When this is done, the parent transform will no longer /// "affect" or offset the transform of the child. The child's parent is /// first checked to make sure it is the parent passed in. If it is, then /// the child's parent property is set to null. If not, the parent property /// on the child is left alone. /// /// The parent to seperate from the child. /// The child to seperate from the parent. public static void SeparateParentChild(ITransform parent, ITransform child) { // ensure these two are actually parented before separating them if (child.TransformParent != null && child.TransformParent == parent) { parent.TransformChildren.Remove(child); child.TransformParent = null; } } /// /// Shallow copies the /// into the . /// Note that, this method will re-parent the child s /// of the to the ; /// This is due to the fact that no can have /// two parents. /// /// The that is to /// take the result of the copy. /// The that is to be copied. public static void ShallowCopy(ITransform destination, ITransform source) { Matrix4 sourceLocalMatrix = source.LocalMatrix; destination.SetTransformation(ref sourceLocalMatrix); destination.Extended = source.TransformParent; foreach (ITransform transformChild in source.TransformChildren) transformChild.Extended = destination; } /// /// The list of or /// instances that have /// attached themselves to this instance. /// public IList Extensions { get; protected set; } /// /// The backing field to . /// bool _isUpdateNeeded; } }