Adding rotation constraint.
diff --git a/Nima-Math-Cpp b/Nima-Math-Cpp
index c17f1d1..488baa0 160000
--- a/Nima-Math-Cpp
+++ b/Nima-Math-Cpp
@@ -1 +1 @@
-Subproject commit c17f1d1a8235fcd1aba6dbb590a5a787e1ad8940
+Subproject commit 488baa0dc91271fc4cd0f5086c837bf38d598007
diff --git a/Source/Actor.cpp b/Source/Actor.cpp
index 6b01296..e5e7fab 100644
--- a/Source/Actor.cpp
+++ b/Source/Actor.cpp
@@ -4,6 +4,8 @@
 #include "ActorIKTarget.hpp"
 #include "ActorIKConstraint.hpp"
 #include "ActorScaleConstraint.hpp"
+#include "ActorTranslationConstraint.hpp"
+#include "ActorRotationConstraint.hpp"
 #include "ActorEvent.hpp"
 #include "ActorNodeSolo.hpp"
 #include "CustomProperty.hpp"
@@ -461,6 +463,13 @@
 			case BlockType::ActorScaleConstraint:
 				component = ActorScaleConstraint::read(this, componentBlock);
 				break;
+			case BlockType::ActorTranslationConstraint:
+				component = ActorTranslationConstraint::read(this, componentBlock);
+				break;
+			case BlockType::ActorRotationConstraint:
+				component = ActorRotationConstraint::read(this, componentBlock);
+				break;
+
 			default:
 				// Not handled/expected block.
 				break;
diff --git a/Source/ActorRotationConstraint.cpp b/Source/ActorRotationConstraint.cpp
new file mode 100644
index 0000000..df3ef3b
--- /dev/null
+++ b/Source/ActorRotationConstraint.cpp
@@ -0,0 +1,184 @@
+#include "ActorRotationConstraint.hpp"
+#include "BlockReader.hpp"
+#include "ActorNode.hpp"
+#include <cmath>
+
+using namespace nima;
+
+ActorRotationConstraint::ActorRotationConstraint() : Base(nullptr, ComponentType::ActorRotationConstraint),
+    m_Copy(false),
+    m_Scale(1.0f),
+    m_EnableMin(false),
+    m_EnableMax(false),
+    m_Min(0.0f),
+    m_Max(0.0f),
+    m_Offset(false),
+    m_SourceSpace(TransformSpace::World),
+    m_DestSpace(TransformSpace::World),
+    m_MinMaxSpace(TransformSpace::World)
+{
+
+}
+
+void ActorRotationConstraint::copy(ActorRotationConstraint* node, Actor* resetActor)
+{
+    Base::copy(node, resetActor);
+
+    m_Copy = node->m_Copy;
+    m_Scale = node->m_Scale;
+    m_EnableMin = node->m_EnableMin;
+    m_EnableMax = node->m_EnableMax;
+    m_Min = node->m_Min;
+    m_Max = node->m_Max;
+
+    m_Offset = node->m_Offset;
+    m_SourceSpace = node->m_SourceSpace;
+    m_DestSpace = node->m_DestSpace;
+    m_MinMaxSpace = node->m_MinMaxSpace;
+}
+
+ActorComponent* ActorRotationConstraint::makeInstance(Actor* resetActor)
+{
+    ActorRotationConstraint* instance = new ActorRotationConstraint();
+    instance->copy(this, resetActor);
+    return instance;
+}
+
+ActorRotationConstraint* ActorRotationConstraint::read(Actor* actor, BlockReader* reader, ActorRotationConstraint* constraint)
+{
+    if(constraint == nullptr)
+    {
+        constraint = new ActorRotationConstraint();
+    }
+    Base::read(actor, reader, constraint);
+
+    if((constraint->m_Copy = reader->readByte() == 1))
+    {
+        constraint->m_Scale = reader->readFloat();
+    }
+    if((constraint->m_EnableMin = reader->readByte() == 1))
+    {
+        constraint->m_Min = reader->readFloat();
+    }
+    if((constraint->m_EnableMax = reader->readByte() == 1))
+    {
+        constraint->m_Max = reader->readFloat();
+    }
+
+    constraint->m_Offset = reader->readByte() == 1;
+    constraint->m_SourceSpace = (TransformSpace)reader->readByte();
+    constraint->m_DestSpace = (TransformSpace)reader->readByte();
+    constraint->m_MinMaxSpace = (TransformSpace)reader->readByte();
+
+    return constraint;
+}
+
+void ActorRotationConstraint::constrain(ActorNode* node)
+{
+    // TODO: Should ActorTargetedConstraint just store targets as nodes?
+    ActorNode* target = static_cast<ActorNode*>(m_Target);
+    ActorNode* grandParent = m_Parent->parent();
+
+    const Mat2D& transformA = m_Parent->worldTransform();
+    Mat2D transformB;
+    Mat2D::decompose(m_ComponentsA, transformA);
+    if(target == nullptr)
+    {
+        Mat2D::copy(transformB, transformA);
+        TransformComponents::copy(m_ComponentsB, m_ComponentsA);
+    }
+    else
+    {
+        Mat2D::copy(transformB, target->worldTransform());
+        if(m_SourceSpace == TransformSpace::Local)
+        {
+            ActorNode* sourceGrandParent = target->parent();
+            if(sourceGrandParent != nullptr)
+            {
+                Mat2D inverse;
+                if(!Mat2D::invert(inverse, sourceGrandParent->worldTransform()))
+                {
+                    return;
+                }
+                Mat2D::multiply(transformB, inverse, transformB);
+            }
+        }
+        Mat2D::decompose(m_ComponentsB, transformB);
+
+        if(!m_Copy)
+        {
+            m_ComponentsB.rotation(m_DestSpace == TransformSpace::Local ? 1.0f : m_ComponentsA.rotation());
+        }
+        else
+        {
+            m_ComponentsB.rotation(m_ComponentsB.rotation() * m_Scale);
+            if(m_Offset)
+            {
+                m_ComponentsB.rotation(m_ComponentsB.rotation() + m_Parent->rotation());
+            }
+        }
+
+        if(m_DestSpace == TransformSpace::Local)
+        {
+            // Destination space is in parent transform coordinates.
+            // Recompose the parent local transform and get it in world, then decompose the world for interpolation.
+            if(grandParent != nullptr)
+            {
+                Mat2D::compose(transformB, m_ComponentsB);
+                Mat2D::multiply(transformB, grandParent->worldTransform(), transformB);
+                Mat2D::decompose(m_ComponentsB, transformB);
+            }
+        }
+    }
+    
+    bool clampLocal = m_MinMaxSpace == TransformSpace::Local && grandParent != nullptr;
+    if(clampLocal)
+    {
+        // Apply min max in local space, so transform to local coordinates first.
+        Mat2D::compose(transformB, m_ComponentsB);
+        Mat2D inverse;
+        if(!Mat2D::invert(inverse, grandParent->worldTransform()))
+        {
+            return;
+        }
+        Mat2D::multiply(transformB, inverse, transformB);
+        Mat2D::decompose(m_ComponentsB, transformB);
+    }
+    if(m_EnableMax && m_ComponentsB.rotation() > m_Max)
+    {
+        m_ComponentsB.rotation(m_Max);	
+    }
+    if(m_EnableMin && m_ComponentsB.rotation() < m_Min)
+    {
+        m_ComponentsB.rotation(m_Min);	
+    }
+    if(clampLocal)
+    {
+        // Transform back to world.
+        Mat2D::compose(transformB, m_ComponentsB);
+        Mat2D::multiply(transformB, grandParent->worldTransform(), transformB);
+        Mat2D::decompose(m_ComponentsB, transformB);
+    }
+
+    float angleA = std::fmod(m_ComponentsA.rotation(), (float)M_PI_2);
+    float angleB = std::fmod(m_ComponentsB.rotation(), (float)M_PI_2);
+    float diff = angleB - angleA;
+    
+    if(diff > M_PI)
+    {
+        diff -= M_PI_2;
+    }
+    else if(diff < -M_PI)
+    {
+        diff += M_PI_2;
+    }
+
+    m_ComponentsB.rotation(m_ComponentsA.rotation() + diff * m_Strength);
+    m_ComponentsB.x(m_ComponentsA.x());
+    m_ComponentsB.y(m_ComponentsA.y());
+    m_ComponentsB.scaleX(m_ComponentsA.scaleX());
+    m_ComponentsB.scaleY(m_ComponentsA.scaleY());
+    m_ComponentsB.skew(m_ComponentsA.skew());
+
+    Mat2D::compose(m_Parent->mutableWorldTransform(), m_ComponentsB);
+}
\ No newline at end of file
diff --git a/Source/ActorRotationConstraint.hpp b/Source/ActorRotationConstraint.hpp
new file mode 100644
index 0000000..38af5d8
--- /dev/null
+++ b/Source/ActorRotationConstraint.hpp
@@ -0,0 +1,36 @@
+#ifndef _NIMA_ACTORROTATIONCONSTRAINT_HPP_
+#define _NIMA_ACTORROTATIONCONSTRAINT_HPP_
+
+#include "ActorTargetedConstraint.hpp"
+#include "TransformSpace.hpp"
+#include "nima/TransformComponents.hpp"
+
+namespace nima
+{
+    class ActorRotationConstraint : public ActorTargetedConstraint
+    {
+        typedef ActorTargetedConstraint Base;
+        private:
+            bool m_Copy;
+            float m_Scale;
+            bool m_EnableMin;
+            bool m_EnableMax;
+            float m_Min;
+            float m_Max;
+            bool m_Offset;
+            TransformSpace m_SourceSpace;
+            TransformSpace m_DestSpace;
+            TransformSpace m_MinMaxSpace;
+            TransformComponents m_ComponentsA;
+            TransformComponents m_ComponentsB;
+
+        public:
+            ActorRotationConstraint();
+            void copy(ActorRotationConstraint* node, Actor* resetActor);
+            ActorComponent* makeInstance(Actor* resetActor) override;
+            static ActorRotationConstraint* read(Actor* actor, BlockReader* reader, ActorRotationConstraint* constraint = nullptr);
+            void constrain(ActorNode* node) override;
+    };
+}
+
+#endif
\ No newline at end of file
diff --git a/Source/ActorTranslationConstraint.cpp b/Source/ActorTranslationConstraint.cpp
new file mode 100644
index 0000000..3dbad73
--- /dev/null
+++ b/Source/ActorTranslationConstraint.cpp
@@ -0,0 +1,137 @@
+#include "ActorTranslationConstraint.hpp"
+
+using namespace nima;
+
+ActorTranslationConstraint::ActorTranslationConstraint() : Base(nullptr, ComponentType::ActorTranslationConstraint)
+{
+
+}
+
+ActorComponent* ActorTranslationConstraint::makeInstance(Actor* resetActor)
+{
+	ActorTranslationConstraint* instanceNode = new ActorTranslationConstraint();
+	instanceNode->copy(this, resetActor);
+	return instanceNode;
+}
+
+ActorTranslationConstraint* ActorTranslationConstraint::read(Actor* actor, BlockReader* reader, ActorTranslationConstraint* constraint)
+{
+    if(constraint == nullptr)
+    {
+        constraint = new ActorTranslationConstraint();
+    }
+
+    Base::read(actor, reader, constraint);
+
+    return constraint;
+}
+
+void ActorTranslationConstraint::constrain(ActorNode* node)
+{
+    // TODO: Should ActorTargetedConstraint just store targets as nodes?
+    ActorNode* target = static_cast<ActorNode*>(m_Target);
+    ActorNode* grandParent = m_Parent->parent();
+
+    Mat2D& transformA = m_Parent->mutableWorldTransform();
+    Vec2D translationA(transformA[4], transformA[5]);
+    Vec2D translationB;
+    if(target == nullptr)
+    {
+        Vec2D::copy(translationB, translationA);
+    }
+    else
+    {
+        Mat2D transformB(target->worldTransform());
+        if(m_SourceSpace == TransformSpace::Local)
+        {
+            ActorNode* sourceGrandParent = target->parent();
+            if(sourceGrandParent != nullptr)
+            {
+                Mat2D inverse;
+                if(!Mat2D::invert(inverse, sourceGrandParent->worldTransform()))
+                {
+                    return;
+                }
+                Mat2D::multiply(transformB, inverse, transformB);
+            }
+        }
+        translationB[0] = transformB[4];
+        translationB[1] = transformB[5];
+
+        if(!m_CopyX)
+        {
+            translationB[0] = m_DestSpace == TransformSpace::Local ? 0.0f : translationA[0];
+        }
+        else
+        {
+            translationB[0] *= m_ScaleX;	
+            if(m_Offset)
+            {
+                translationB[0] += m_Parent->x();
+            }
+        }
+
+        if(!m_CopyY)
+        {
+            translationB[1] = m_DestSpace == TransformSpace::Local ? 0.0f : translationA[1];
+        }
+        else
+        {
+            translationB[1] *= m_ScaleY;
+
+            if(m_Offset)
+            {
+                translationB[1] += m_Parent->y();
+            }
+        }
+
+        if(m_DestSpace == TransformSpace::Local)
+        {
+            // Destination space is in parent transform coordinates.
+            if(grandParent != nullptr)
+            {
+                Vec2D::transform(translationB, translationB, grandParent->worldTransform());
+            }
+        }
+    }
+    
+    bool clampLocal = m_MinMaxSpace == TransformSpace::Local && grandParent != nullptr;
+    if(clampLocal)
+    {
+        // Apply min max in local space, so transform to local coordinates first.
+        Mat2D invert;
+        if(!Mat2D::invert(invert, grandParent->worldTransform()))
+        {
+            return;
+        }
+        // Get our target world coordinates in parent local.
+        Vec2D::transform(translationB, translationB, invert);
+    }
+    if(m_EnableMaxX && translationB[0] > m_MaxX)
+    {
+        translationB[0] = m_MaxX;	
+    }
+    if(m_EnableMinX && translationB[0] < m_MinX)
+    {
+        translationB[0] = m_MinX;	
+    }
+    if(m_EnableMaxY && translationB[1] > m_MaxY)
+    {
+        translationB[1] = m_MaxY;	
+    }
+    if(m_EnableMinY && translationB[1] < m_MinY)
+    {
+        translationB[1] = m_MinY;	
+    }
+    if(clampLocal)
+    {
+        // Transform back to world.
+        Vec2D::transform(translationB, translationB, grandParent->worldTransform());
+    }
+
+    float ti = 1.0f-m_Strength;
+
+    // Just interpolate world translation
+    transformA[4] = translationA[0] * ti + translationB[0] * m_Strength;
+    transformA[5] = translationA[1] * ti + translationB[1] * m_Strength;
+}
\ No newline at end of file
diff --git a/Source/ActorTranslationConstraint.hpp b/Source/ActorTranslationConstraint.hpp
new file mode 100644
index 0000000..246ef93
--- /dev/null
+++ b/Source/ActorTranslationConstraint.hpp
@@ -0,0 +1,20 @@
+#ifndef _NIMA_ACTORTRANSLATIONCONSTRAINT_HPP_
+#define _NIMA_ACTORTRANSLATIONCONSTRAINT_HPP_
+
+#include "ActorAxisConstraint.hpp"
+
+namespace nima
+{
+    class ActorTranslationConstraint : public ActorAxisConstraint
+    {
+        typedef ActorAxisConstraint Base;
+
+        public:
+            ActorTranslationConstraint();
+            ActorComponent* makeInstance(Actor* resetActor) override;
+            static ActorTranslationConstraint* read(Actor* actor, BlockReader* reader, ActorTranslationConstraint* constraint = nullptr);
+            void constrain(ActorNode* node) override;
+    };
+}
+
+#endif
\ No newline at end of file