Adding Jelly Bone support.
diff --git a/Nima-Math-Cpp b/Nima-Math-Cpp
index d413bc2..faab42f 160000
--- a/Nima-Math-Cpp
+++ b/Nima-Math-Cpp
@@ -1 +1 @@
-Subproject commit d413bc24bbbd6cf5601f72d1f33e080a462b5167
+Subproject commit faab42f0d1b63e1df73f5c69944ac5ff9c6ca6e2
diff --git a/Source/Actor.cpp b/Source/Actor.cpp
index d2162ce..d9a7365 100644
--- a/Source/Actor.cpp
+++ b/Source/Actor.cpp
@@ -9,6 +9,8 @@
 #include "ActorDistanceConstraint.hpp"
 #include "ActorTransformConstraint.hpp"
 #include "ActorEvent.hpp"
+#include "ActorJellyBone.hpp"
+#include "JellyComponent.hpp"
 #include "ActorNodeSolo.hpp"
 #include "CustomProperty.hpp"
 #include "ActorCollider.hpp"
@@ -477,6 +479,12 @@
 			case BlockType::ActorDistanceConstraint:
 				component = ActorDistanceConstraint::read(this, componentBlock);
 				break;
+			case BlockType::ActorJellyBone:
+				component = ActorJellyBone::read(this, componentBlock);
+				break;
+			case BlockType::JellyComponent:
+				component = JellyComponent::read(this, componentBlock);
+				break;
 			default:
 				// Not handled/expected block.
 				break;
diff --git a/Source/ActorBone.cpp b/Source/ActorBone.cpp
index 0a8685c..ea1642a 100644
--- a/Source/ActorBone.cpp
+++ b/Source/ActorBone.cpp
@@ -3,66 +3,10 @@
 
 using namespace nima;
 
-ActorBone::ActorBone() : ActorNode(ComponentType::ActorBone), m_Length(0.0f), m_IsConnectedToImage(false)
+ActorBone::ActorBone() : Base(ComponentType::ActorBone), m_FirstBone(nullptr), m_Jelly(nullptr)
 {
 }
 
-float ActorBone::length() const
-{
-	return m_Length;
-}
-
-void ActorBone::length(float l)
-{
-	if(m_Length == l)
-	{
-		return;
-	}
-	m_Length = l;
-	for(ActorNode* node : m_Children)
-	{
-		if(node->type() == ComponentType::ActorBone)
-		{
-			ActorBone* bone = reinterpret_cast<ActorBone*>(node);
-			bone->x(l);
-		}
-	}
-}
-
-bool ActorBone::isConnectedToImage() const
-{
-	return m_IsConnectedToImage;
-}
-
-void ActorBone::isConnectedToImage(bool isIt)
-{
-	m_IsConnectedToImage = isIt;
-}
-
-Vec2D ActorBone::tipWorldTranslation()
-{
-	Mat2D transform;
-	transform[4] = m_Length;
-
-	Mat2D::multiply(transform, worldTransform(), transform);
-
-	Vec2D result;
-	result[0] = transform[4];
-	result[1] = transform[5];
-
-	return result;
-}
-
-void ActorBone::tipWorldTranslation(Vec2D& result)
-{
-	Mat2D transform;
-	transform[4] = m_Length;
-
-	Mat2D::multiply(transform, worldTransform(), transform);
-	result[0] = transform[4];
-	result[1] = transform[5];
-}
-
 ActorBone* ActorBone::read(Actor* actor, BlockReader* reader, ActorBone* node)
 {
 	if(node == nullptr)
@@ -70,9 +14,7 @@
 		node = new ActorBone();
 	}
 
-	ActorNode::read(actor, reader, node);
-
-	node->m_Length = reader->readFloat();
+	Base::read(actor, reader, node);
 
 	return node;
 }
@@ -84,9 +26,19 @@
 	return instanceNode;
 }
 
-void ActorBone::copy(ActorBone* node, Actor* resetActor)
+void ActorBone::completeResolve()
 {
-	ActorNode::copy(node, resetActor);
-	m_Length = node->m_Length;
-	m_IsConnectedToImage = node->m_IsConnectedToImage;
-}
+	Base::completeResolve();
+	for(ActorNode* node : m_Children)
+	{
+		if(node->type() == ComponentType::ActorBone)
+		{
+			ActorBone* bone = static_cast<ActorBone*>(node);	
+			if(bone != nullptr)
+			{
+				m_FirstBone = bone;
+				break;
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/Source/ActorBone.hpp b/Source/ActorBone.hpp
index 80cc27f..5d32b9a 100644
--- a/Source/ActorBone.hpp
+++ b/Source/ActorBone.hpp
@@ -1,36 +1,28 @@
 #ifndef _NIMA_ACTORBONE_HPP_
 #define _NIMA_ACTORBONE_HPP_
 
-#include "ActorNode.hpp"
-#include <nima/Vec2D.hpp>
-
+#include "ActorBoneBase.hpp"
 
 namespace nima
 {
 	class Actor;
 	class BlockReader;
-	class ActorNode;
+	class JellyComponent;
 
-	class ActorBone : public ActorNode
+	class ActorBone : public ActorBoneBase
 	{
-		typedef ActorNode Base;
+		typedef ActorBoneBase Base;
+		friend class JellyComponent;
+		
 		protected:
-			float m_Length;
-			bool m_IsConnectedToImage;
+			ActorBone* m_FirstBone;
+			JellyComponent* m_Jelly;
 
 		public:
 			ActorBone();
-			float length() const;
-			void length(float l);
-			bool isConnectedToImage() const;
-			void isConnectedToImage(bool isIt);
-			void tipWorldTranslation(Vec2D& result);
-			Vec2D tipWorldTranslation();
-
 			ActorComponent* makeInstance(Actor* resetActor) override;
-			void copy(ActorBone* node, Actor* resetActor);
-
-			static ActorBone* read(Actor* actor, BlockReader* reader, ActorBone* node = NULL);
+			void completeResolve() override;
+			static ActorBone* read(Actor* actor, BlockReader* reader, ActorBone* node = nullptr);
 	};
 }
 #endif
\ No newline at end of file
diff --git a/Source/ActorBoneBase.cpp b/Source/ActorBoneBase.cpp
new file mode 100644
index 0000000..e2f12fc
--- /dev/null
+++ b/Source/ActorBoneBase.cpp
@@ -0,0 +1,81 @@
+#include "ActorBoneBase.hpp"
+#include "BlockReader.hpp"
+
+using namespace nima;
+
+ActorBoneBase::ActorBoneBase(ComponentType type) : ActorNode(type), m_Length(0.0f), m_IsConnectedToImage(false)
+{
+}
+
+float ActorBoneBase::length() const
+{
+	return m_Length;
+}
+
+void ActorBoneBase::length(float l)
+{
+	if(m_Length == l)
+	{
+		return;
+	}
+	m_Length = l;
+	for(ActorNode* node : m_Children)
+	{
+        ActorBoneBase* bone = dynamic_cast<ActorBoneBase*>(node);
+        if(bone == nullptr)
+        {
+            continue;
+        }
+		bone->x(l);
+	}
+}
+
+bool ActorBoneBase::isConnectedToImage() const
+{
+	return m_IsConnectedToImage;
+}
+
+void ActorBoneBase::isConnectedToImage(bool isIt)
+{
+	m_IsConnectedToImage = isIt;
+}
+
+Vec2D ActorBoneBase::tipWorldTranslation()
+{
+	Mat2D transform;
+	transform[4] = m_Length;
+
+	Mat2D::multiply(transform, worldTransform(), transform);
+
+	Vec2D result;
+	result[0] = transform[4];
+	result[1] = transform[5];
+
+	return result;
+}
+
+void ActorBoneBase::tipWorldTranslation(Vec2D& result)
+{
+	Mat2D transform;
+	transform[4] = m_Length;
+
+	Mat2D::multiply(transform, worldTransform(), transform);
+	result[0] = transform[4];
+	result[1] = transform[5];
+}
+
+ActorBoneBase* ActorBoneBase::read(Actor* actor, BlockReader* reader, ActorBoneBase* node)
+{
+	ActorNode::read(actor, reader, node);
+
+	node->m_Length = reader->readFloat();
+
+	return node;
+}
+
+void ActorBoneBase::copy(ActorBoneBase* node, Actor* resetActor)
+{
+	ActorNode::copy(node, resetActor);
+	m_Length = node->m_Length;
+	m_IsConnectedToImage = node->m_IsConnectedToImage;
+}
diff --git a/Source/ActorBoneBase.hpp b/Source/ActorBoneBase.hpp
new file mode 100644
index 0000000..04a2421
--- /dev/null
+++ b/Source/ActorBoneBase.hpp
@@ -0,0 +1,32 @@
+#ifndef _NIMA_ACTORBONEBASE_HPP_
+#define _NIMA_ACTORBONEBASE_HPP_
+
+#include "ActorNode.hpp"
+#include <nima/Vec2D.hpp>
+
+namespace nima
+{
+	class Actor;
+	class BlockReader;
+	class ActorNode;
+
+	class ActorBoneBase : public ActorNode
+	{
+		typedef ActorNode Base;
+		protected:
+			float m_Length;
+			bool m_IsConnectedToImage;
+
+		public:
+			ActorBoneBase(ComponentType type);
+			float length() const;
+			void length(float l);
+			bool isConnectedToImage() const;
+			void isConnectedToImage(bool isIt);
+			void tipWorldTranslation(Vec2D& result);
+			Vec2D tipWorldTranslation();
+			void copy(ActorBoneBase* node, Actor* resetActor);
+			static ActorBoneBase* read(Actor* actor, BlockReader* reader, ActorBoneBase* node = NULL);
+	};
+}
+#endif
\ No newline at end of file
diff --git a/Source/ActorImage.cpp b/Source/ActorImage.cpp
index 9eb30f2..d98bb77 100644
--- a/Source/ActorImage.cpp
+++ b/Source/ActorImage.cpp
@@ -1,5 +1,5 @@
 #include "ActorImage.hpp"
-#include "ActorBone.hpp"
+#include "ActorBoneBase.hpp"
 #include "BlockReader.hpp"
 #include "Actor.hpp"
 #include <cstring>
@@ -288,7 +288,7 @@
 		{
 			BoneConnection& bc = m_BoneConnections[i];
 
-			ActorBone* bone = dynamic_cast<ActorBone*>(components[bc.boneIndex]);
+			ActorBoneBase* bone = dynamic_cast<ActorBoneBase*>(components[bc.boneIndex]);
 			if(bone != nullptr)
 			{
 				bc.node = bone;
diff --git a/Source/ActorJellyBone.cpp b/Source/ActorJellyBone.cpp
new file mode 100644
index 0000000..d9caca2
--- /dev/null
+++ b/Source/ActorJellyBone.cpp
@@ -0,0 +1,31 @@
+#include "ActorJellyBone.hpp"
+#include "BlockReader.hpp"
+
+using namespace nima;
+
+ActorJellyBone::ActorJellyBone() : Base(ComponentType::ActorJellyBone)
+{
+
+}
+
+ActorComponent* ActorJellyBone::makeInstance(Actor* resetActor)
+{
+	ActorJellyBone* instanceNode = new ActorJellyBone();
+	instanceNode->copy(this, resetActor);
+	return instanceNode;
+}
+
+ActorJellyBone* ActorJellyBone::read(Actor* actor, BlockReader* reader, ActorJellyBone* node)
+{
+    if(node == nullptr)
+    {
+        node = new ActorJellyBone();
+    }
+
+    // The Jelly Bone has a specialized read that doesn't go down the typical node path, this is because majority of the transform properties
+    // of the Jelly Bone are controlled by the Jelly Controller and are unnecessary for serialization.
+    ActorComponent::read(actor, reader, node);
+    node->m_Opacity = reader->readFloat();
+    node->m_IsCollapsedVisibility = reader->readByte() == 1;
+    return node;
+}
\ No newline at end of file
diff --git a/Source/ActorJellyBone.hpp b/Source/ActorJellyBone.hpp
new file mode 100644
index 0000000..d7541b6
--- /dev/null
+++ b/Source/ActorJellyBone.hpp
@@ -0,0 +1,21 @@
+#ifndef _NIMA_ACTORJELLYBONE_HPP_
+#define _NIMA_ACTORJELLYBONE_HPP_
+
+#include "ActorBoneBase.hpp"
+
+namespace nima
+{
+	class Actor;
+	class BlockReader;
+
+	class ActorJellyBone : public ActorBoneBase
+	{
+		typedef ActorBoneBase Base;
+
+		public:
+			ActorJellyBone();
+			ActorComponent* makeInstance(Actor* resetActor) override;
+			static ActorJellyBone* read(Actor* actor, BlockReader* reader, ActorJellyBone* node = nullptr);
+	};
+}
+#endif
\ No newline at end of file
diff --git a/Source/ActorNode.cpp b/Source/ActorNode.cpp
index b5aef9d..83bc239 100644
--- a/Source/ActorNode.cpp
+++ b/Source/ActorNode.cpp
@@ -77,6 +77,21 @@
 	markTransformDirty();
 }
 
+const Vec2D& ActorNode::translation() const
+{
+	return m_Translation;
+}
+
+void ActorNode::translation(const Vec2D& v)
+{
+	if(m_Translation[0] == v[0] && m_Translation[1] == v[1])
+	{
+		return;
+	}
+	Vec2D::copy(m_Translation, v);
+	markTransformDirty();
+}
+
 float ActorNode::x() const
 {
 	return m_Translation[0];
diff --git a/Source/ActorNode.hpp b/Source/ActorNode.hpp
index dcafc6d..45844f3 100644
--- a/Source/ActorNode.hpp
+++ b/Source/ActorNode.hpp
@@ -26,7 +26,7 @@
 			float m_Opacity;
 			float m_RenderOpacity;
 
-		private:
+		protected:
 			bool m_OverrideWorldTransform;
 			bool m_IsCollapsedVisibility;
 			bool m_RenderCollapsed;
@@ -49,6 +49,8 @@
 			static const unsigned char TransformDirty = 1<<0;
 			static const unsigned char WorldTransformDirty = 1<<1;
 
+			const Vec2D& translation() const;
+			void translation(const Vec2D& v);
 			float x() const;
 			void x(float v);
 			float y() const;
@@ -82,7 +84,7 @@
 			bool addConstraint(ActorConstraint* constraint);
 			void update(unsigned char dirt) override;
 
-			const std::vector<ActorNode*> children() const { return m_Children; }
+			const std::vector<ActorNode*>& children() const { return m_Children; }
 	};
 }
 #endif
\ No newline at end of file
diff --git a/Source/JellyComponent.cpp b/Source/JellyComponent.cpp
new file mode 100644
index 0000000..fc17b68
--- /dev/null
+++ b/Source/JellyComponent.cpp
@@ -0,0 +1,384 @@
+#include "JellyComponent.hpp"
+#include "ActorNode.hpp"
+#include "ActorBone.hpp"
+#include "ActorJellyBone.hpp"
+#include "BlockReader.hpp"
+
+using namespace nima;
+
+const float JellyComponent::OptimalDistance = 4.0f*(std::sqrt(2.0f)-1.0f)/3.0f;
+const float JellyComponent::CurveConstant = JellyComponent::OptimalDistance * std::sqrt(2.0f) * 0.5f;
+
+JellyComponent::JellyComponent() : Base(ComponentType::JellyComponent),
+    m_EaseIn(0.0f),
+    m_EaseOut(0.0f),
+    m_ScaleIn(0.0f),
+    m_ScaleOut(0.0f),
+    m_InTargetIdx(0),
+    m_OutTargetIdx(0),
+    m_InTarget(nullptr),
+    m_OutTarget(nullptr),
+    m_CachedScaleIn(0.0f),
+    m_CachedScaleOut(0.0f)
+{
+    m_NormalizedCurvePoints.reserve(JellyMax);
+}
+
+ActorComponent* JellyComponent::makeInstance(Actor* resetActor)
+{
+    JellyComponent* instanceNode = new JellyComponent();
+    instanceNode->copy(this, resetActor);
+    return instanceNode;
+}
+
+JellyComponent* JellyComponent::read(Actor* actor, BlockReader* reader, JellyComponent* node)
+{
+    if(node == nullptr)
+    {
+        node = new JellyComponent();
+    }
+    
+    Base::read(actor, reader, node);
+    
+    node->m_EaseIn = reader->readFloat();
+    node->m_EaseOut = reader->readFloat();
+    node->m_ScaleIn = reader->readFloat();
+    node->m_ScaleOut = reader->readFloat();
+    node->m_InTargetIdx = reader->readUnsignedShort();
+    node->m_OutTargetIdx = reader->readUnsignedShort();
+
+    return node;
+}
+
+void JellyComponent::copy(JellyComponent* node, Actor* resetActor)
+{
+    Base::copy(node, resetActor);
+    m_EaseIn = node->m_EaseIn;
+    m_EaseOut = node->m_EaseOut;
+    m_ScaleIn = node->m_ScaleIn;
+    m_ScaleOut = node->m_ScaleOut;
+    m_InTargetIdx = node->m_InTargetIdx;
+    m_OutTargetIdx = node->m_OutTargetIdx;
+}
+
+void JellyComponent::resolveComponentIndices(ActorComponent** components)
+{
+    Base::resolveComponentIndices(components);
+
+    if(m_InTargetIdx != 0)
+    {
+        m_InTarget = dynamic_cast<ActorNode*>(components[m_InTargetIdx]);
+    }
+    if(m_OutTargetIdx != 0)
+    {
+        m_OutTarget = dynamic_cast<ActorNode*>(components[m_OutTargetIdx]);
+    }
+}
+
+void JellyComponent::completeResolve()
+{
+    Base::completeResolve();
+
+    ActorBone* bone = dynamic_cast<ActorBone*>(m_Parent);
+    assert(bone != nullptr);
+    bone->m_Jelly = this;
+
+    // Get jellies.
+    const std::vector<ActorNode*>& children = bone->children();
+    for(ActorNode* child : children)
+    {
+        if(child->type() == ComponentType::ActorJellyBone)
+        {
+            m_Bones.push_back(static_cast<ActorJellyBone*>(child));
+        }
+    }    
+}
+
+static const float EPSILON = 0.001f; // Intentionally agressive.
+
+static bool fuzzyEquals(Vec2D a, Vec2D b) 
+{
+    float a0 = a[0], a1 = a[1];
+    float b0 = b[0], b1 = b[1];
+    return (std::abs(a0 - b0) <= EPSILON*std::max(1.0f, std::max(std::abs(a0), std::abs(b0))) &&
+            std::abs(a1 - b1) <= EPSILON*std::max(1.0f, std::max(std::abs(a1), std::abs(b1))));
+}
+
+static void forwardDiffBezier(float c0, float c1, float c2, float c3, Vec2D* points, int count, int offset)
+{
+    float f = (float)count;
+
+    float p0 = c0;
+
+    float p1 = 3.0f * (c1 - c0) / f;
+
+    f *= count;
+    float p2 = 3.0f * (c0 - 2.0f * c1 + c2) / f;
+    
+    f *= count;
+    float p3 = (c3 - c0 + 3.0f * (c1 - c2)) / f;
+
+    c0 = p0;
+    c1 = p1 + p2 + p3;
+    c2 = 2 * p2 + 6 * p3;
+    c3 = 6 * p3;
+
+    for (int a = 0; a <= count; a++) 
+    {
+        points[a][offset] = c0;
+        c0 += c1;
+        c1 += c2;
+        c2 += c3;
+    }
+}
+
+// IList<Vec2D> NormalizeCurve(Vec2D[] curve, int numSegments)
+// {
+//     IList<Vec2D> points = new List<Vec2D>();
+//     int curvePointCount = curve.Length;
+//     float[] distances = new float[curvePointCount];
+//     distances[0] = 0;
+//     for(int i = 0; i < curvePointCount-1; i++)
+//     {
+//         Vec2D p1 = curve[i];
+//         Vec2D p2 = curve[i+1];
+//         distances[i + 1] = distances[i] + Vec2D.Distance(p1, p2);
+//     }
+//     float totalDistance = distances[curvePointCount-1];
+
+//     float segmentLength = totalDistance/numSegments;
+//     int pointIndex = 1;
+//     for(int i = 1; i <= numSegments; i++)
+//     {
+//         float distance = segmentLength * i;
+
+//         while(pointIndex < curvePointCount-1 && distances[pointIndex] < distance)
+//         {
+//             pointIndex++;
+//         }
+
+//         float d = distances[pointIndex];
+//         float lastCurveSegmentLength = d - distances[pointIndex-1];
+//         float remainderOfDesired = d - distance;
+//         float ratio = remainderOfDesired / lastCurveSegmentLength;
+//         float iratio = 1.0f-ratio;
+
+//         Vec2D p1 = curve[pointIndex-1];
+//         Vec2D p2 = curve[pointIndex];
+//         points.Add(new Vec2D(p1[0]*ratio+p2[0]*iratio, p1[1]*ratio+p2[1]*iratio));
+//     }
+
+//     return points;
+// }
+
+void normalizeCurve(std::vector<Vec2D>& points, Vec2D* curve, const int numSegments)
+{
+    constexpr int curvePointCount = JellyComponent::JellyMax+1;
+    float distances[curvePointCount];
+    distances[0] = 0;
+    for(int i = 0; i < curvePointCount-1; i++)
+    {
+        const Vec2D& p1 = curve[i];
+        const Vec2D& p2 = curve[i+1];
+        distances[i + 1] = distances[i] + Vec2D::distance(p1, p2);
+    }
+    float totalDistance = distances[curvePointCount-1];
+    float segmentLength = totalDistance/numSegments;
+    int pointIndex = 1;
+    points.resize(numSegments);
+    for(int i = 1; i <= numSegments; i++)
+    {
+        float distance = segmentLength * i;
+
+        while(pointIndex < curvePointCount-1 && distances[pointIndex] < distance)
+        {
+            pointIndex++;
+        }
+
+        float d = distances[pointIndex];
+        float lastCurveSegmentLength = d - distances[pointIndex-1];
+        float remainderOfDesired = d - distance;
+        float ratio = remainderOfDesired / lastCurveSegmentLength;
+        float iratio = 1.0f-ratio;
+
+        const Vec2D& p1 = curve[pointIndex-1];
+        const Vec2D& p2 = curve[pointIndex];
+
+        Vec2D& point = points[i-1];
+        point[0] = p1[0]*ratio+p2[0]*iratio;
+        point[1] = p1[1]*ratio+p2[1]*iratio;
+    }
+}
+
+void JellyComponent::updateJellies()
+{
+    ActorBone* bone = static_cast<ActorBone*>(m_Parent);
+    // We are in local bone space.
+    Vec2D tipPosition(bone->length(), 0.0f);
+
+    if(fuzzyEquals(m_CachedTip, tipPosition) && fuzzyEquals(m_CachedOut, m_OutPoint) && fuzzyEquals(m_CachedIn, m_InPoint) && m_CachedScaleIn == m_ScaleIn && m_CachedScaleOut == m_ScaleOut)
+    {
+        return;
+    }
+
+    Vec2D::copy(m_CachedTip, tipPosition);
+    Vec2D::copy(m_CachedOut, m_OutPoint);
+    Vec2D::copy(m_CachedIn, m_InPoint);
+    m_CachedScaleIn = m_ScaleIn;
+    m_CachedScaleOut = m_ScaleOut;
+
+    Vec2D q0;
+    Vec2D q1 = m_InPoint;
+    Vec2D q2 = m_OutPoint;
+    Vec2D q3 = tipPosition;
+
+    forwardDiffBezier(q0[0], q1[0], q2[0], q3[0], m_JellyPoints, JellyMax, 0);
+    forwardDiffBezier(q0[1], q1[1], q2[1], q3[1], m_JellyPoints, JellyMax, 1);
+
+    normalizeCurve(m_NormalizedCurvePoints, m_JellyPoints, m_Bones.size());
+    // IList<Vec2D> normalizedPoints = normalizeCurve(m_JellyPoints, m_Bones.size());
+
+    Vec2D lastPoint = m_JellyPoints[0];
+
+    float scale = m_ScaleIn;
+    float scaleInc = (m_ScaleOut - m_ScaleIn)/(float)(m_Bones.size()-1);
+    for(int i = 0, length = m_NormalizedCurvePoints.size(); i < length; i++)
+    {
+        ActorJellyBone* jelly = m_Bones[i];
+        const Vec2D& p = m_NormalizedCurvePoints[i];
+
+        jelly->translation(lastPoint);
+        jelly->length(Vec2D::distance(p, lastPoint));
+        jelly->scaleY(scale);
+        scale += scaleInc;
+
+        Vec2D diff;
+        Vec2D::subtract(diff, p, lastPoint);
+        jelly->rotation(std::atan2(diff[1], diff[0]));
+        lastPoint = p;
+    }
+}
+
+void JellyComponent::update(unsigned char dirt)
+{
+    ActorBone* bone = static_cast<ActorBone*>(m_Parent);
+    ActorNode* parentBoneNode = bone->parent();
+    ActorBone* parentBone;
+    if(parentBoneNode != nullptr && parentBoneNode->type() == ComponentType::ActorBone)
+    {
+        parentBone = static_cast<ActorBone*>(parentBoneNode);
+    }
+    else
+    {
+        parentBone = nullptr;
+    }
+    
+    JellyComponent* parentBoneJelly = parentBone == nullptr ? nullptr : parentBone->m_Jelly;
+
+    Mat2D inverseWorld;
+    if(!Mat2D::invert(inverseWorld, bone->worldTransform()))
+    {
+        return;
+    }
+
+    if(m_InTarget != nullptr)
+    {
+        Vec2D translation;
+        m_InTarget->worldTranslation(translation);
+
+        Vec2D::transform(m_InPoint, translation, inverseWorld);
+        Vec2D::normalize(m_InDirection, m_InPoint);
+    }
+    else if(parentBone != nullptr)
+    {
+        if(parentBone->m_FirstBone == bone && parentBoneJelly != nullptr && parentBoneJelly->m_OutTarget != nullptr)
+        {
+            Vec2D translation;
+            parentBoneJelly->m_OutTarget->worldTranslation(translation);
+
+            Vec2D localParentOut;
+            Vec2D::transform(localParentOut, translation, inverseWorld);
+            Vec2D::normalize(localParentOut, localParentOut);
+            Vec2D::negate(m_InDirection, localParentOut);
+        }
+        else
+        {
+            Vec2D d1(1.0f, 0.0f);
+            Vec2D d2(1.0f, 0.0f);
+
+            Vec2D::transformDir(d1, d1, parentBone->worldTransform());
+            Vec2D::transformDir(d2, d2, bone->worldTransform());
+
+            Vec2D sum;
+            Vec2D::add(sum, d1, d2);
+            Vec2D::transformDir(m_InDirection, sum, inverseWorld);
+            Vec2D::normalize(m_InDirection, m_InDirection);
+        }
+        m_InPoint[0] = m_InDirection[0] * m_EaseIn * bone->length() * CurveConstant;
+        m_InPoint[1] = m_InDirection[1] * m_EaseIn * bone->length() * CurveConstant;
+    }
+    else
+    {
+        m_InDirection[0] = 1.0f;
+        m_InDirection[1] = 0.0f;
+        m_InPoint[0] = m_InDirection[0] * m_EaseIn * bone->length() * CurveConstant;
+    }
+
+    if(m_OutTarget != nullptr)
+    {
+        Vec2D translation;
+        m_OutTarget->worldTranslation(translation);
+        Vec2D::transform(m_OutPoint, translation, inverseWorld);
+        Vec2D tip(bone->length(), 0.0f);
+        Vec2D::subtract(m_OutDirection, m_OutPoint, tip);
+        Vec2D::normalize(m_OutDirection, m_OutDirection);
+    }
+    else if(bone->m_FirstBone != nullptr)
+    {
+        ActorBone* firstBone = bone->m_FirstBone;
+        JellyComponent* firstBoneJelly = firstBone->m_Jelly;
+        if(firstBoneJelly != nullptr && firstBoneJelly->m_InTarget != nullptr)
+        {
+            Vec2D translation;
+            firstBoneJelly->m_InTarget->worldTranslation(translation);
+
+            Vec2D worldChildInDir;
+            firstBone->worldTranslation(worldChildInDir);
+            Vec2D::subtract(worldChildInDir, worldChildInDir, translation);
+            Vec2D::transformDir(m_OutDirection, worldChildInDir, inverseWorld);
+        }
+        else
+        {
+            Vec2D d1(1.0f, 0.0f);
+            Vec2D d2(1.0f, 0.0f);
+
+            Vec2D::transformDir(d1, d1, firstBone->worldTransform());
+            Vec2D::transformDir(d2, d2, bone->worldTransform());
+
+            Vec2D sum;
+            Vec2D::add(sum, d1, d2);
+            Vec2D::negate(sum, sum);
+            Vec2D::transformDir(m_OutDirection, sum, inverseWorld);
+            Vec2D::normalize(m_OutDirection, m_OutDirection);
+        }
+        Vec2D::normalize(m_OutDirection, m_OutDirection);
+        Vec2D scaledOut;
+        Vec2D::scale(scaledOut, m_OutDirection, m_EaseOut*bone->length()*CurveConstant);
+        m_OutPoint[0] = bone->length();
+        m_OutPoint[1] = 0.0f;
+        Vec2D::add(m_OutPoint, m_OutPoint, scaledOut);
+    }
+    else
+    {
+        m_OutDirection[0] = -1.0f;
+        m_OutDirection[1] = 0.0f;
+
+        Vec2D scaledOut;
+        Vec2D::scale(scaledOut, m_OutDirection, m_EaseOut*bone->length()*CurveConstant);
+        m_OutPoint[0] = bone->length();
+        m_OutPoint[1] = 0.0f;
+        Vec2D::add(m_OutPoint, m_OutPoint, scaledOut);
+    }
+
+    updateJellies();
+}
\ No newline at end of file
diff --git a/Source/JellyComponent.hpp b/Source/JellyComponent.hpp
new file mode 100644
index 0000000..bc85beb
--- /dev/null
+++ b/Source/JellyComponent.hpp
@@ -0,0 +1,59 @@
+#ifndef _NIMA_JELLYCOMPONENT_HPP_
+#define _NIMA_JELLYCOMPONENT_HPP_
+
+#include "ActorComponent.hpp"
+#include "nima/Vec2D.hpp"
+#include <vector>
+#include <cmath>
+
+namespace nima
+{
+	class Actor;
+	class BlockReader;
+    class ActorJellyBone;
+
+	class JellyComponent : public ActorComponent
+	{
+		typedef ActorComponent Base;
+
+        public:
+            static constexpr int JellyMax = 16;
+            static const float OptimalDistance;
+            static const float CurveConstant;
+
+        private:
+            float m_EaseIn;
+            float m_EaseOut;
+            float m_ScaleIn;
+            float m_ScaleOut;
+            unsigned short m_InTargetIdx;
+            unsigned short m_OutTargetIdx;
+            ActorNode* m_InTarget;
+            ActorNode* m_OutTarget;
+            std::vector<ActorJellyBone*> m_Bones;
+            Vec2D m_InPoint;
+            Vec2D m_InDirection;
+            Vec2D m_OutPoint;
+            Vec2D m_OutDirection;
+
+            Vec2D m_CachedTip;
+            Vec2D m_CachedOut;
+            Vec2D m_CachedIn;
+            float m_CachedScaleIn;
+            float m_CachedScaleOut;
+
+            Vec2D m_JellyPoints[JellyMax+1];
+            std::vector<Vec2D> m_NormalizedCurvePoints;
+
+		public:
+			JellyComponent();
+			ActorComponent* makeInstance(Actor* resetActor) override;
+			void copy(JellyComponent* node, Actor* resetActor);
+			static JellyComponent* read(Actor* actor, BlockReader* reader, JellyComponent* node = nullptr);
+			void resolveComponentIndices(ActorComponent** components) override;
+			void completeResolve() override;
+			void updateJellies();
+            void update(unsigned char dirt) override;
+	};
+}
+#endif
\ No newline at end of file