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;
}
}