using System;
using System.ComponentModel;
using Microsoft.Xna.Framework;
namespace MonoGame.Extended
{
// Code derived from top answer: http://gamedev.stackexchange.com/questions/113977/should-i-store-local-forward-right-up-vector-or-calculate-when-necessary
[Flags]
internal enum TransformFlags : byte
{
WorldMatrixIsDirty = 1 << 0,
LocalMatrixIsDirty = 1 << 1,
All = WorldMatrixIsDirty | LocalMatrixIsDirty
}
///
/// Represents the base class for the position, rotation, and scale of a game object in two-dimensions or
/// three-dimensions.
///
/// The type of the matrix.
///
///
/// Every game object has a transform which is used to store and manipulate the position, rotation and scale
/// of the object. Every transform can have a parent, which allows to apply position, rotation and scale to game
/// objects hierarchically.
///
///
/// This class shouldn't be used directly. Instead use either of the derived classes; or
/// Transform3D.
///
///
[EditorBrowsable(EditorBrowsableState.Never)]
public abstract class BaseTransform
where TMatrix : struct
{
private TransformFlags _flags = TransformFlags.All; // dirty flags, set all dirty flags when created
private TMatrix _localMatrix; // model space to local space
private BaseTransform _parent; // parent
private TMatrix _worldMatrix; // local space to world space
// internal contructor because people should not be using this class directly; they should use Transform2D or Transform3D
internal BaseTransform()
{
}
///
/// Gets the model-to-local space .
///
///
/// The model-to-local space .
///
public TMatrix LocalMatrix
{
get
{
RecalculateLocalMatrixIfNecessary(); // attempt to update local matrix upon request if it is dirty
return _localMatrix;
}
}
///
/// Gets the local-to-world space .
///
///
/// The local-to-world space .
///
public TMatrix WorldMatrix
{
get
{
RecalculateWorldMatrixIfNecessary(); // attempt to update world matrix upon request if it is dirty
return _worldMatrix;
}
}
///
/// Gets or sets the parent instance.
///
///
/// The parent instance.
///
///
///
/// Setting to a non-null instance enables this instance to
/// inherit the position, rotation, and scale of the parent instance. Setting to
/// null disables the inheritance altogether for this instance.
///
///
[EditorBrowsable(EditorBrowsableState.Never)]
public BaseTransform Parent
{
get { return _parent; }
set
{
if (_parent == value)
return;
var oldParentTransform = Parent;
_parent = value;
OnParentChanged(oldParentTransform, value);
}
}
public event Action TransformBecameDirty; // observer pattern for when the world (or local) matrix became dirty
public event Action TranformUpdated; // observer pattern for after the world (or local) matrix was re-calculated
///
/// Gets the model-to-local space .
///
/// The model-to-local space .
public void GetLocalMatrix(out TMatrix matrix)
{
RecalculateLocalMatrixIfNecessary();
matrix = _localMatrix;
}
///
/// Gets the local-to-world space .
///
/// The local-to-world space .
public void GetWorldMatrix(out TMatrix matrix)
{
RecalculateWorldMatrixIfNecessary();
matrix = _worldMatrix;
}
protected internal void LocalMatrixBecameDirty()
{
_flags |= TransformFlags.LocalMatrixIsDirty;
}
protected internal void WorldMatrixBecameDirty()
{
_flags |= TransformFlags.WorldMatrixIsDirty;
TransformBecameDirty?.Invoke();
}
private void OnParentChanged(BaseTransform oldParent, BaseTransform newParent)
{
var parent = oldParent;
while (parent != null)
{
parent.TransformBecameDirty -= ParentOnTransformBecameDirty;
parent = parent.Parent;
}
parent = newParent;
while (parent != null)
{
parent.TransformBecameDirty += ParentOnTransformBecameDirty;
parent = parent.Parent;
}
}
private void ParentOnTransformBecameDirty()
{
_flags |= TransformFlags.All;
}
private void RecalculateWorldMatrixIfNecessary()
{
if ((_flags & TransformFlags.WorldMatrixIsDirty) == 0)
return;
RecalculateLocalMatrixIfNecessary();
RecalculateWorldMatrix(ref _localMatrix, out _worldMatrix);
_flags &= ~TransformFlags.WorldMatrixIsDirty;
TranformUpdated?.Invoke();
}
protected internal abstract void RecalculateWorldMatrix(ref TMatrix localMatrix, out TMatrix matrix);
private void RecalculateLocalMatrixIfNecessary()
{
if ((_flags & TransformFlags.LocalMatrixIsDirty) == 0)
return;
RecalculateLocalMatrix(out _localMatrix);
_flags &= ~TransformFlags.LocalMatrixIsDirty;
WorldMatrixBecameDirty();
}
protected internal abstract void RecalculateLocalMatrix(out TMatrix matrix);
}
///
/// Represents the position, rotation, and scale of a two-dimensional game object.
///
///
///
///
///
///
///
/// Every game object has a transform which is used to store and manipulate the position, rotation and scale
/// of the object. Every transform can have a parent, which allows to apply position, rotation and scale to game
/// objects hierarchically.
///
///
public class Transform2 : BaseTransform, IMovable, IRotatable, IScalable
{
private Vector2 _position;
private float _rotation;
private Vector2 _scale = Vector2.One;
public Transform2()
: this(Vector2.Zero, 0, Vector2.One)
{
}
public Transform2(float x, float y, float rotation = 0, float scaleX = 1, float scaleY = 1)
: this(new Vector2(x, y), rotation, new Vector2(scaleX, scaleY))
{
}
public Transform2(Vector2? position = null, float rotation = 0, Vector2? scale = null)
{
Position = position ?? Vector2.Zero;
Rotation = rotation;
Scale = scale ?? Vector2.One;
}
///
/// Gets the world position.
///
///
/// The world position.
///
public Vector2 WorldPosition => WorldMatrix.Translation;
///
/// Gets the world scale.
///
///
/// The world scale.
///
public Vector2 WorldScale => WorldMatrix.Scale;
///
/// Gets the world rotation angle in radians.
///
///
/// The world rotation angle in radians.
///
public float WorldRotation => WorldMatrix.Rotation;
///
/// Gets or sets the local position.
///
///
/// The local position.
///
public Vector2 Position
{
get { return _position; }
set
{
_position = value;
LocalMatrixBecameDirty();
WorldMatrixBecameDirty();
}
}
///
/// Gets or sets the local rotation angle in radians.
///
///
/// The local rotation angle in radians.
///
public float Rotation
{
get { return _rotation; }
set
{
_rotation = value;
LocalMatrixBecameDirty();
WorldMatrixBecameDirty();
}
}
///
/// Gets or sets the local scale.
///
///
/// The local scale.
///
public Vector2 Scale
{
get { return _scale; }
set
{
_scale = value;
LocalMatrixBecameDirty();
WorldMatrixBecameDirty();
}
}
protected internal override void RecalculateWorldMatrix(ref Matrix3x2 localMatrix, out Matrix3x2 matrix)
{
if (Parent != null)
{
Parent.GetWorldMatrix(out matrix);
Matrix3x2.Multiply(ref localMatrix, ref matrix, out matrix);
}
else
{
matrix = localMatrix;
}
}
protected internal override void RecalculateLocalMatrix(out Matrix3x2 matrix)
{
matrix = Matrix3x2.CreateScale(_scale) *
Matrix3x2.CreateRotationZ(_rotation) *
Matrix3x2.CreateTranslation(_position);
}
public override string ToString()
{
return $"Position: {Position}, Rotation: {Rotation}, Scale: {Scale}";
}
}
///
/// Represents the position, rotation, and scale of a three-dimensional game object.
///
///
///
///
/// Every game object has a transform which is used to store and manipulate the position, rotation and scale
/// of the object. Every transform can have a parent, which allows to apply position, rotation and scale to game
/// objects hierarchically.
///
///
public class Transform3 : BaseTransform {
private Vector3 _position;
private Quaternion _rotation;
private Vector3 _scale = Vector3.One;
public Transform3(Vector3? position = null, Quaternion? rotation = null, Vector3? scale = null) {
Position = position ?? Vector3.Zero;
Rotation = rotation ?? Quaternion.Identity;
Scale = scale ?? Vector3.One;
}
///
/// Gets the world position.
///
///
/// The world position.
///
public Vector3 WorldPosition => WorldMatrix.Translation;
///
/// Gets the world scale.
///
///
/// The world scale.
///
public Vector3 WorldScale {
get {
Vector3 scale = Vector3.Zero;
Quaternion rotation = Quaternion.Identity;
Vector3 translation = Vector3.Zero;
WorldMatrix.Decompose(out scale, out rotation, out translation);
return scale;
}
}
///
/// Gets the world rotation quaternion in radians.
///
///
/// The world rotation quaternion in radians.
///
public Quaternion WorldRotation {
get {
Vector3 scale = Vector3.Zero;
Quaternion rotation = Quaternion.Identity;
Vector3 translation = Vector3.Zero;
WorldMatrix.Decompose(out scale, out rotation, out translation);
return rotation;
}
}
///
/// Gets or sets the local position.
///
///
/// The local position.
///
public Vector3 Position {
get { return _position; }
set {
_position = value;
LocalMatrixBecameDirty();
WorldMatrixBecameDirty();
}
}
///
/// Gets or sets the local rotation quaternion in radians.
///
///
/// The local rotation quaternion in radians.
///
public Quaternion Rotation {
get { return _rotation; }
set {
_rotation = value;
LocalMatrixBecameDirty();
WorldMatrixBecameDirty();
}
}
///
/// Gets or sets the local scale.
///
///
/// The local scale.
///
public Vector3 Scale {
get { return _scale; }
set {
_scale = value;
LocalMatrixBecameDirty();
WorldMatrixBecameDirty();
}
}
protected internal override void RecalculateWorldMatrix(ref Matrix localMatrix, out Matrix matrix) {
if (Parent != null) {
Parent.GetWorldMatrix(out matrix);
Matrix.Multiply(ref localMatrix, ref matrix, out matrix);
}
else {
matrix = localMatrix;
}
}
protected internal override void RecalculateLocalMatrix(out Matrix matrix) {
matrix = Matrix.CreateScale(_scale) *
Matrix.CreateFromQuaternion(_rotation) *
Matrix.CreateTranslation(_position);
}
public override string ToString() {
return $"Position: {Position}, Rotation: {Rotation}, Scale: {Scale}";
}
}
}