diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json
index 35f2d4b9af..1412b45968 100644
--- a/interface/resources/avatar/avatar-animation.json
+++ b/interface/resources/avatar/avatar-animation.json
@@ -103,8 +103,8 @@
                                     "rotationVar": "spine2Rotation",
                                     "typeVar": "spine2Type",
                                     "weightVar": "spine2Weight",
-                                    "weight": 1.0,
-                                    "flexCoefficients": [1.0, 0.5, 0.5]
+                                    "weight": 2.0,
+                                    "flexCoefficients": [1.0, 0.5, 0.25]
                                 },
                                 {
                                     "jointName": "Head",
@@ -113,7 +113,7 @@
                                     "typeVar": "headType",
                                     "weightVar": "headWeight",
                                     "weight": 4.0,
-                                    "flexCoefficients": [1, 0.05, 0.25, 0.25, 0.25]
+                                    "flexCoefficients": [1, 0.5, 0.25, 0.2, 0.1]
                                 },
                                 {
                                     "jointName": "LeftArm",
diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp
index 5b5d70be01..77437e79b9 100644
--- a/libraries/animation/src/AnimInverseKinematics.cpp
+++ b/libraries/animation/src/AnimInverseKinematics.cpp
@@ -20,6 +20,8 @@
 #include "ElbowConstraint.h"
 #include "SwingTwistConstraint.h"
 #include "AnimationLogging.h"
+#include "CubicHermiteSpline.h"
+#include "AnimUtil.h"
 
 AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn,
                                                 const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector<float>& flexCoefficientsIn) :
@@ -59,7 +61,8 @@ AnimInverseKinematics::AnimInverseKinematics(const QString& id) : AnimNode(AnimN
 
 AnimInverseKinematics::~AnimInverseKinematics() {
     clearConstraints();
-    _accumulators.clear();
+    _rotationAccumulators.clear();
+    _translationAccumulators.clear();
     _targetVarVec.clear();
 }
 
@@ -72,10 +75,12 @@ void AnimInverseKinematics::loadPoses(const AnimPoseVec& poses) {
     assert(_skeleton && ((poses.size() == 0) || (_skeleton->getNumJoints() == (int)poses.size())));
     if (_skeleton->getNumJoints() == (int)poses.size()) {
         _relativePoses = poses;
-        _accumulators.resize(_relativePoses.size());
+        _rotationAccumulators.resize(_relativePoses.size());
+        _translationAccumulators.resize(_relativePoses.size());
     } else {
         _relativePoses.clear();
-        _accumulators.clear();
+        _rotationAccumulators.clear();
+        _translationAccumulators.clear();
     }
 }
 
@@ -175,14 +180,17 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::
     }
 }
 
-void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext& context, const std::vector<IKTarget>& targets) {
+void AnimInverseKinematics::solve(const AnimContext& context, const std::vector<IKTarget>& targets) {
     // compute absolute poses that correspond to relative target poses
     AnimPoseVec absolutePoses;
     absolutePoses.resize(_relativePoses.size());
     computeAbsolutePoses(absolutePoses);
 
     // clear the accumulators before we start the IK solver
-    for (auto& accumulator: _accumulators) {
+    for (auto& accumulator : _rotationAccumulators) {
+        accumulator.clearAndClean();
+    }
+    for (auto& accumulator : _translationAccumulators) {
         accumulator.clearAndClean();
     }
 
@@ -197,14 +205,22 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext&
 
         // solve all targets
         for (auto& target: targets) {
-            solveTargetWithCCD(context, target, absolutePoses, debug);
+            if (target.getType() == IKTarget::Type::Spline) {
+                solveTargetWithSpline(context, target, absolutePoses, debug);
+            } else {
+                solveTargetWithCCD(context, target, absolutePoses, debug);
+            }
         }
 
         // harvest accumulated rotations and apply the average
         for (int i = 0; i < (int)_relativePoses.size(); ++i) {
-            if (_accumulators[i].size() > 0) {
-                _relativePoses[i].rot() = _accumulators[i].getAverage();
-                _accumulators[i].clear();
+            if (_rotationAccumulators[i].size() > 0) {
+                _relativePoses[i].rot() = _rotationAccumulators[i].getAverage();
+                _rotationAccumulators[i].clear();
+            }
+            if (_translationAccumulators[i].size() > 0) {
+                _relativePoses[i].trans() = _translationAccumulators[i].getAverage();
+                _translationAccumulators[i].clear();
             }
         }
 
@@ -236,7 +252,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext&
         int parentIndex = _skeleton->getParentIndex(tipIndex);
 
         // update rotationOnly targets that don't lie on the ik chain of other ik targets.
-        if (parentIndex != -1 && !_accumulators[tipIndex].isDirty() && target.getType() == IKTarget::Type::RotationOnly) {
+        if (parentIndex != -1 && !_rotationAccumulators[tipIndex].isDirty() && target.getType() == IKTarget::Type::RotationOnly) {
             const glm::quat& targetRotation = target.getRotation();
             // compute tip's new parent-relative rotation
             // Q = Qp * q   -->   q' = Qp^ * Q
@@ -311,10 +327,13 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
         }
 
         // store the relative rotation change in the accumulator
-        _accumulators[tipIndex].add(tipRelativeRotation, target.getWeight());
+        _rotationAccumulators[tipIndex].add(tipRelativeRotation, target.getWeight());
+
+        glm::vec3 tipRelativeTranslation = _relativePoses[target.getIndex()].trans();
+        _translationAccumulators[tipIndex].add(tipRelativeTranslation);
 
         if (debug) {
-            debugJointMap[tipIndex] = DebugJoint(tipRelativeRotation, constrained);
+            debugJointMap[tipIndex] = DebugJoint(tipRelativeRotation, tipRelativeTranslation, constrained);
         }
     }
 
@@ -422,10 +441,13 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
         }
 
         // store the relative rotation change in the accumulator
-        _accumulators[pivotIndex].add(newRot, target.getWeight());
+        _rotationAccumulators[pivotIndex].add(newRot, target.getWeight());
+
+        glm::vec3 newTrans = _relativePoses[pivotIndex].trans();
+        _translationAccumulators[pivotIndex].add(newTrans);
 
         if (debug) {
-            debugJointMap[pivotIndex] = DebugJoint(newRot, constrained);
+            debugJointMap[pivotIndex] = DebugJoint(newRot, newTrans, constrained);
         }
 
         // keep track of tip's new transform as we descend towards root
@@ -444,6 +466,187 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
     }
 }
 
+static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const AnimPose& tipPose, const AnimPose& basePose, float baseGain = 1.0f, float tipGain = 1.0f) {
+    float linearDistance = glm::length(basePose.trans() - tipPose.trans());
+    glm::vec3 p0 = basePose.trans();
+    glm::vec3 m0 = baseGain * linearDistance * (basePose.rot() * Vectors::UNIT_Y);
+    glm::vec3 p1 = tipPose.trans();
+    glm::vec3 m1 = tipGain * linearDistance * (tipPose.rot() * Vectors::UNIT_Y);
+
+    return CubicHermiteSplineFunctorWithArcLength(p0, m0, p1, m1);
+}
+
+// pre-compute information about each joint influeced by this spline IK target.
+void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) {
+    std::vector<SplineJointInfo> splineJointInfoVec;
+
+    // build spline between the default poses.
+    AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex());
+    AnimPose basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex);
+
+    CubicHermiteSplineFunctorWithArcLength spline;
+    if (target.getIndex() == _headIndex) {
+        // set gain factors so that more curvature occurs near the tip of the spline.
+        const float HIPS_GAIN = 0.5f;
+        const float HEAD_GAIN = 1.0f;
+        spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN);
+    } else {
+        spline = computeSplineFromTipAndBase(tipPose, basePose);
+    }
+
+    // measure the total arc length along the spline
+    float totalArcLength = spline.arcLength(1.0f);
+
+    glm::vec3 baseToTip = tipPose.trans() - basePose.trans();
+    float baseToTipLength = glm::length(baseToTip);
+    glm::vec3 baseToTipNormal = baseToTip / baseToTipLength;
+
+    int index = target.getIndex();
+    int endIndex = _skeleton->getParentIndex(_hipsIndex);
+    while (index != endIndex) {
+        AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index);
+
+        float ratio = glm::dot(defaultPose.trans() - basePose.trans(), baseToTipNormal) / baseToTipLength;
+
+        // compute offset from spline to the default pose.
+        float t = spline.arcLengthInverse(ratio * totalArcLength);
+
+        // compute the rotation by using the derivative of the spline as the y-axis, and the defaultPose x-axis
+        glm::vec3 y = glm::normalize(spline.d(t));
+        glm::vec3 x = defaultPose.rot() * Vectors::UNIT_X;
+        glm::vec3 u, v, w;
+        generateBasisVectors(y, x, v, u, w);
+        glm::mat3 m(u, v, glm::cross(u, v));
+        glm::quat rot = glm::normalize(glm::quat_cast(m));
+
+        AnimPose pose(glm::vec3(1.0f), rot, spline(t));
+        AnimPose offsetPose = pose.inverse() * defaultPose;
+
+        SplineJointInfo splineJointInfo = { index, ratio, offsetPose };
+        splineJointInfoVec.push_back(splineJointInfo);
+        index = _skeleton->getParentIndex(index);
+    }
+
+    _splineJointInfoMap[target.getIndex()] = splineJointInfoVec;
+}
+
+const std::vector<AnimInverseKinematics::SplineJointInfo>* AnimInverseKinematics::findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target) {
+    // find or create splineJointInfo for this target
+    auto iter = _splineJointInfoMap.find(target.getIndex());
+    if (iter != _splineJointInfoMap.end()) {
+        return &(iter->second);
+    } else {
+        computeSplineJointInfosForIKTarget(context, target);
+        auto iter = _splineJointInfoMap.find(target.getIndex());
+        if (iter != _splineJointInfoMap.end()) {
+            return &(iter->second);
+        }
+    }
+
+    return nullptr;
+}
+
+void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug) {
+
+    std::map<int, DebugJoint> debugJointMap;
+
+    const int baseIndex = _hipsIndex;
+
+    // build spline from tip to base
+    AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation());
+    AnimPose basePose = absolutePoses[baseIndex];
+    CubicHermiteSplineFunctorWithArcLength spline;
+    if (target.getIndex() == _headIndex) {
+        // set gain factors so that more curvature occurs near the tip of the spline.
+        const float HIPS_GAIN = 0.5f;
+        const float HEAD_GAIN = 1.0f;
+        spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN);
+    } else {
+        spline = computeSplineFromTipAndBase(tipPose, basePose);
+    }
+    float totalArcLength = spline.arcLength(1.0f);
+
+    // This prevents the rotation interpolation from rotating the wrong physical way (but correct mathematical way)
+    // when the head is arched backwards very far.
+    glm::quat halfRot = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), 0.5f));
+    if (glm::dot(halfRot * Vectors::UNIT_Z, basePose.rot() * Vectors::UNIT_Z) < 0.0f) {
+        tipPose.rot() = -tipPose.rot();
+    }
+
+    // find or create splineJointInfo for this target
+    const std::vector<SplineJointInfo>* splineJointInfoVec = findOrCreateSplineJointInfo(context, target);
+
+    if (splineJointInfoVec && splineJointInfoVec->size() > 0) {
+        const int baseParentIndex = _skeleton->getParentIndex(baseIndex);
+        AnimPose parentAbsPose = (baseParentIndex >= 0) ? absolutePoses[baseParentIndex] : AnimPose();
+
+        // go thru splineJointInfoVec backwards (base to tip)
+        for (int i = (int)splineJointInfoVec->size() - 1; i >= 0; i--) {
+            const SplineJointInfo& splineJointInfo = (*splineJointInfoVec)[i];
+            float t = spline.arcLengthInverse(splineJointInfo.ratio * totalArcLength);
+            glm::vec3 trans = spline(t);
+
+            // for head splines, preform most twist toward the tip by using ease in function. t^2
+            float rotT = t;
+            if (target.getIndex() == _headIndex) {
+                rotT = t * t;
+            }
+            glm::quat twistRot = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), rotT));
+
+            // compute the rotation by using the derivative of the spline as the y-axis, and the twistRot x-axis
+            glm::vec3 y = glm::normalize(spline.d(t));
+            glm::vec3 x = twistRot * Vectors::UNIT_X;
+            glm::vec3 u, v, w;
+            generateBasisVectors(y, x, v, u, w);
+            glm::mat3 m(u, v, glm::cross(u, v));
+            glm::quat rot = glm::normalize(glm::quat_cast(m));
+
+            AnimPose desiredAbsPose = AnimPose(glm::vec3(1.0f), rot, trans) * splineJointInfo.offsetPose;
+
+            // apply flex coefficent
+            AnimPose flexedAbsPose;
+            ::blend(1, &absolutePoses[splineJointInfo.jointIndex], &desiredAbsPose, target.getFlexCoefficient(i), &flexedAbsPose);
+
+            AnimPose relPose = parentAbsPose.inverse() * flexedAbsPose;
+            _rotationAccumulators[splineJointInfo.jointIndex].add(relPose.rot(), target.getWeight());
+
+            bool constrained = false;
+            if (splineJointInfo.jointIndex != _hipsIndex) {
+                // constrain the amount the spine can stretch or compress
+                float length = glm::length(relPose.trans());
+                const float EPSILON = 0.0001f;
+                if (length > EPSILON) {
+                    float defaultLength = glm::length(_skeleton->getRelativeDefaultPose(splineJointInfo.jointIndex).trans());
+                    const float STRETCH_COMPRESS_PERCENTAGE = 0.15f;
+                    const float MAX_LENGTH = defaultLength * (1.0f + STRETCH_COMPRESS_PERCENTAGE);
+                    const float MIN_LENGTH = defaultLength * (1.0f - STRETCH_COMPRESS_PERCENTAGE);
+                    if (length > MAX_LENGTH) {
+                        relPose.trans() = (relPose.trans() / length) * MAX_LENGTH;
+                        constrained = true;
+                    } else if (length < MIN_LENGTH) {
+                        relPose.trans() = (relPose.trans() / length) * MIN_LENGTH;
+                        constrained = true;
+                    }
+                } else {
+                    relPose.trans() = glm::vec3(0.0f);
+                }
+            }
+
+            _translationAccumulators[splineJointInfo.jointIndex].add(relPose.trans(), target.getWeight());
+
+            if (debug) {
+                debugJointMap[splineJointInfo.jointIndex] = DebugJoint(relPose.rot(), relPose.trans(), constrained);
+            }
+
+            parentAbsPose = flexedAbsPose;
+        }
+    }
+
+    if (debug) {
+        debugDrawIKChain(debugJointMap, context);
+    }
+}
+
 //virtual
 const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) {
     // don't call this function, call overlay() instead
@@ -456,10 +659,6 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
     // allows solutionSource to be overridden by an animVar
     auto solutionSource = animVars.lookup(_solutionSourceVar, (int)_solutionSource);
 
-    if (context.getEnableDebugDrawIKConstraints()) {
-        debugDrawConstraints(context);
-    }
-
     const float MAX_OVERLAY_DT = 1.0f / 30.0f; // what to clamp delta-time to in AnimInverseKinematics::overlay
     if (dt > MAX_OVERLAY_DT) {
         dt = MAX_OVERLAY_DT;
@@ -568,7 +767,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
 
             {
                 PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0);
-                solveWithCyclicCoordinateDescent(context, targets);
+                solve(context, targets);
             }
 
             if (_hipsTargetIndex < 0) {
@@ -578,6 +777,10 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
                 _hipsOffset = Vectors::ZERO;
             }
         }
+
+        if (context.getEnableDebugDrawIKConstraints()) {
+            debugDrawConstraints(context);
+        }
     }
 
     if (_leftHandIndex > -1) {
@@ -1057,7 +1260,10 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele
 
     _maxTargetIndex = -1;
 
-    for (auto& accumulator: _accumulators) {
+    for (auto& accumulator: _rotationAccumulators) {
+        accumulator.clearAndClean();
+    }
+    for (auto& accumulator: _translationAccumulators) {
         accumulator.clearAndClean();
     }
 
@@ -1138,6 +1344,7 @@ void AnimInverseKinematics::debugDrawIKChain(std::map<int, DebugJoint>& debugJoi
     // copy debug joint rotations into the relative poses
     for (auto& debugJoint : debugJointMap) {
         poses[debugJoint.first].rot() = debugJoint.second.relRot;
+        poses[debugJoint.first].trans() = debugJoint.second.relTrans;
     }
 
     // convert relative poses to absolute
@@ -1303,7 +1510,7 @@ void AnimInverseKinematics::blendToPoses(const AnimPoseVec& targetPoses, const A
     int numJoints = (int)_relativePoses.size();
     for (int i = 0; i < numJoints; ++i) {
         float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot(), targetPoses[i].rot()));
-        if (_accumulators[i].isDirty()) {
+        if (_rotationAccumulators[i].isDirty()) {
             // this joint is affected by IK --> blend toward the targetPoses rotation
             _relativePoses[i].rot() = glm::normalize(glm::lerp(_relativePoses[i].rot(), dotSign * targetPoses[i].rot(), blendFactor));
         } else {
@@ -1337,3 +1544,46 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s
         break;
     }
 }
+
+void AnimInverseKinematics::debugDrawSpineSplines(const AnimContext& context, const std::vector<IKTarget>& targets) const {
+
+    for (auto& target : targets) {
+
+        if (target.getType() != IKTarget::Type::Spline) {
+            continue;
+        }
+
+        const int baseIndex = _hipsIndex;
+
+        // build spline
+        AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation());
+        AnimPose basePose = _skeleton->getAbsolutePose(baseIndex, _relativePoses);
+
+        CubicHermiteSplineFunctorWithArcLength spline;
+        if (target.getIndex() == _headIndex) {
+            // set gain factors so that more curvature occurs near the tip of the spline.
+            const float HIPS_GAIN = 0.5f;
+            const float HEAD_GAIN = 1.0f;
+            spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN);
+        } else {
+            spline = computeSplineFromTipAndBase(tipPose, basePose);
+        }
+        float totalArcLength = spline.arcLength(1.0f);
+
+        const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f);
+        const glm::vec4 WHITE(1.0f, 1.0f, 1.0f, 1.0f);
+
+        // draw red and white stripped spline, parameterized by arc length.
+        // i.e. each stripe should be the same length.
+        AnimPose geomToWorldPose = AnimPose(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix());
+        const int NUM_SEGMENTS = 20;
+        const float dArcLength = totalArcLength / NUM_SEGMENTS;
+        float arcLength = 0.0f;
+        for (int i = 0; i < NUM_SEGMENTS; i++) {
+            float prevT = spline.arcLengthInverse(arcLength);
+            float nextT = spline.arcLengthInverse(arcLength + dArcLength);
+            DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(spline(prevT)), geomToWorldPose.xformPoint(spline(nextT)), (i % 2) == 0 ? RED : WHITE);
+            arcLength += dArcLength;
+        }
+    }
+}
diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h
index 35845224e2..cc919c1684 100644
--- a/libraries/animation/src/AnimInverseKinematics.h
+++ b/libraries/animation/src/AnimInverseKinematics.h
@@ -19,6 +19,7 @@
 #include "IKTarget.h"
 
 #include "RotationAccumulator.h"
+#include "TranslationAccumulator.h"
 
 class RotationConstraint;
 
@@ -62,21 +63,33 @@ public:
 
 protected:
     void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses);
-    void solveWithCyclicCoordinateDescent(const AnimContext& context, const std::vector<IKTarget>& targets);
+    void solve(const AnimContext& context, const std::vector<IKTarget>& targets);
     void solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug);
+    void solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug);
     virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override;
     struct DebugJoint {
         DebugJoint() : relRot(), constrained(false) {}
-        DebugJoint(const glm::quat& relRotIn, bool constrainedIn) : relRot(relRotIn), constrained(constrainedIn) {}
+        DebugJoint(const glm::quat& relRotIn, const glm::vec3& relTransIn, bool constrainedIn) : relRot(relRotIn), relTrans(relTransIn), constrained(constrainedIn) {}
         glm::quat relRot;
+        glm::vec3 relTrans;
         bool constrained;
     };
     void debugDrawIKChain(std::map<int, DebugJoint>& debugJointMap, const AnimContext& context) const;
     void debugDrawRelativePoses(const AnimContext& context) const;
     void debugDrawConstraints(const AnimContext& context) const;
+    void debugDrawSpineSplines(const AnimContext& context, const std::vector<IKTarget>& targets) const;
     void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose);
     void blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPose, float blendFactor);
 
+    // used to pre-compute information about each joint influeced by a spline IK target.
+    struct SplineJointInfo {
+        int jointIndex;       // joint in the skeleton that this information pertains to.
+        float ratio;          // percentage (0..1) along the spline for this joint.
+        AnimPose offsetPose;  // local offset from the spline to the joint.
+    };
+
+    void computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target);
+    const std::vector<SplineJointInfo>* findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target);
 
     // for AnimDebugDraw rendering
     virtual const AnimPoseVec& getPosesInternal() const override { return _relativePoses; }
@@ -109,12 +122,15 @@ protected:
     };
 
     std::map<int, RotationConstraint*> _constraints;
-    std::vector<RotationAccumulator> _accumulators;
+    std::vector<RotationAccumulator> _rotationAccumulators;
+    std::vector<TranslationAccumulator> _translationAccumulators;
     std::vector<IKTargetVar> _targetVarVec;
     AnimPoseVec _defaultRelativePoses; // poses of the relaxed state
     AnimPoseVec _relativePoses; // current relative poses
     AnimPoseVec _limitCenterPoses;  // relative
 
+    std::map<int, std::vector<SplineJointInfo>> _splineJointInfoMap;
+
     // experimental data for moving hips during IK
     glm::vec3 _hipsOffset { Vectors::ZERO };
     float _maxHipsOffsetLength{ FLT_MAX };
diff --git a/libraries/animation/src/IKTarget.cpp b/libraries/animation/src/IKTarget.cpp
index 2fe767b08d..41cac62fa3 100644
--- a/libraries/animation/src/IKTarget.cpp
+++ b/libraries/animation/src/IKTarget.cpp
@@ -44,6 +44,9 @@ void IKTarget::setType(int type) {
         case (int)Type::HipsRelativeRotationAndPosition:
             _type = Type::HipsRelativeRotationAndPosition;
             break;
+        case (int)Type::Spline:
+            _type = Type::Spline;
+            break;
         default:
             _type = Type::Unknown;
     }
diff --git a/libraries/animation/src/IKTarget.h b/libraries/animation/src/IKTarget.h
index 4f464c103c..011175aedf 100644
--- a/libraries/animation/src/IKTarget.h
+++ b/libraries/animation/src/IKTarget.h
@@ -21,6 +21,7 @@ public:
         RotationOnly,
         HmdHead,
         HipsRelativeRotationAndPosition,
+        Spline,
         Unknown
     };
 
diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp
index 6a53f6c0f2..20a2aab2b6 100644
--- a/libraries/animation/src/Rig.cpp
+++ b/libraries/animation/src/Rig.cpp
@@ -402,16 +402,6 @@ void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, flo
     }
 }
 
-void Rig::restoreJointRotation(int index, float fraction, float priority) {
-    // AJT: DEAD CODE?
-    ASSERT(false);
-}
-
-void Rig::restoreJointTranslation(int index, float fraction, float priority) {
-    // AJT: DEAD CODE?
-    ASSERT(false);
-}
-
 bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const {
     if (isIndexValid(jointIndex)) {
         position = (rotation * _internalPoseSet._absolutePoses[jointIndex].trans()) + translation;
@@ -1041,8 +1031,8 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) {
         _animVars.set("hipsType", (int)IKTarget::Type::Unknown);
     }
 
-    if (params.spine2Enabled) {
-        _animVars.set("spine2Type", (int)IKTarget::Type::RotationAndPosition);
+    if (params.hipsEnabled && params.spine2Enabled) {
+        _animVars.set("spine2Type", (int)IKTarget::Type::Spline);
         _animVars.set("spine2Position", extractTranslation(params.spine2Matrix));
         _animVars.set("spine2Rotation", glmExtractRotation(params.spine2Matrix));
     } else {
@@ -1052,7 +1042,7 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) {
     if (params.leftArmEnabled) {
         _animVars.set("leftArmType", (int)IKTarget::Type::RotationAndPosition);
         _animVars.set("leftArmPosition", params.leftArmPosition);
-        _animVars.set("leftArmRotation", params.leftArmRotation);        
+        _animVars.set("leftArmRotation", params.leftArmRotation);
     } else {
         _animVars.set("leftArmType", (int)IKTarget::Type::Unknown);
     }
@@ -1102,9 +1092,9 @@ void Rig::updateHeadAnimVars(const HeadParameters& params) {
             _animVars.set("headPosition", params.rigHeadPosition);
             _animVars.set("headRotation", params.rigHeadOrientation);
             if (params.hipsEnabled) {
-                // Since there is an explicit hips ik target, switch the head to use the more generic RotationAndPosition IK chain type.
-                // this will allow the spine to bend more, ensuring that it can reach the head target position.
-                _animVars.set("headType", (int)IKTarget::Type::RotationAndPosition);
+                // Since there is an explicit hips ik target, switch the head to use the more flexible Spline IK chain type.
+                // this will allow the spine to compress/expand and bend more natrually, ensuring that it can reach the head target position.
+                _animVars.set("headType", (int)IKTarget::Type::Spline);
                 _animVars.unset("headWeight");  // use the default weight for this target.
             } else {
                 // When there is no hips IK target, use the HmdHead IK chain type.  This will make the spine very stiff,
diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h
index 0911f330f0..994bd4b074 100644
--- a/libraries/animation/src/Rig.h
+++ b/libraries/animation/src/Rig.h
@@ -134,10 +134,6 @@ public:
     void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority);
     void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority);
 
-    // legacy
-    void restoreJointRotation(int index, float fraction, float priority);
-    void restoreJointTranslation(int index, float fraction, float priority);
-
     // if translation and rotation is identity, position will be in rig space
     bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position,
                                       glm::vec3 translation, glm::quat rotation) const;
diff --git a/libraries/animation/src/RotationAccumulator.cpp b/libraries/animation/src/RotationAccumulator.cpp
index a4940e1989..2b8fc7d66a 100644
--- a/libraries/animation/src/RotationAccumulator.cpp
+++ b/libraries/animation/src/RotationAccumulator.cpp
@@ -1,5 +1,5 @@
 //
-//  RotationAccumulator.h
+//  RotationAccumulator.cpp
 //
 //  Copyright 2015 High Fidelity, Inc.
 //
@@ -27,7 +27,7 @@ void RotationAccumulator::clear() {
     _numRotations = 0;
 }
 
-void RotationAccumulator::clearAndClean() { 
+void RotationAccumulator::clearAndClean() {
     clear();
     _isDirty = false;
 }
diff --git a/libraries/animation/src/TranslationAccumulator.cpp b/libraries/animation/src/TranslationAccumulator.cpp
new file mode 100644
index 0000000000..4b110b1564
--- /dev/null
+++ b/libraries/animation/src/TranslationAccumulator.cpp
@@ -0,0 +1,34 @@
+//
+//  TranslationAccumulator.cpp
+//
+//  Copyright 2015 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "TranslationAccumulator.h"
+
+void TranslationAccumulator::add(const glm::vec3& translation, float weight) {
+    _accum += weight * translation;
+    _totalWeight += weight;
+    _isDirty = true;
+}
+
+glm::vec3 TranslationAccumulator::getAverage() {
+    if (_totalWeight > 0.0f) {
+        return _accum / _totalWeight;
+    } else {
+        return glm::vec3();
+    }
+}
+
+void TranslationAccumulator::clear() {
+    _accum *= 0.0f;
+    _totalWeight = 0.0f;
+}
+
+void TranslationAccumulator::clearAndClean() {
+    clear();
+    _isDirty = false;
+}
diff --git a/libraries/animation/src/TranslationAccumulator.h b/libraries/animation/src/TranslationAccumulator.h
new file mode 100644
index 0000000000..18cac5ec7a
--- /dev/null
+++ b/libraries/animation/src/TranslationAccumulator.h
@@ -0,0 +1,42 @@
+//
+//  TranslationAccumulator.h
+//
+//  Copyright 2017 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi_TranslationAccumulator_h
+#define hifi_TranslationAccumulator_h
+
+#include <glm/glm.hpp>
+
+class TranslationAccumulator {
+public:
+    TranslationAccumulator() : _accum(0.0f, 0.0f, 0.0f), _totalWeight(0), _isDirty(false) { }
+
+    int size() const { return _totalWeight > 0.0f; }
+
+    /// \param translation translation to add
+    /// \param weight contribution factor of this translation to total accumulation
+    void add(const glm::vec3& translation, float weight = 1.0f);
+
+    glm::vec3 getAverage();
+
+    /// \return true if any translation were accumulated
+    bool isDirty() const { return _isDirty; }
+
+    /// \brief clear accumulated translation but don't change _isDirty
+    void clear();
+
+    /// \brief clear accumulated translation and set _isDirty to false
+    void clearAndClean();
+
+private:
+    glm::vec3 _accum;
+    float _totalWeight;
+    bool _isDirty;
+};
+
+#endif // hifi_TranslationAccumulator_h
diff --git a/libraries/shared/src/CubicHermiteSpline.h b/libraries/shared/src/CubicHermiteSpline.h
new file mode 100644
index 0000000000..da2ed26de4
--- /dev/null
+++ b/libraries/shared/src/CubicHermiteSpline.h
@@ -0,0 +1,113 @@
+//
+//  CubicHermiteSpline.h
+//
+//  Copyright 2017 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi_CubicHermiteSpline_h
+#define hifi_CubicHermiteSpline_h
+
+#include "GLMHelpers.h"
+
+class CubicHermiteSplineFunctor {
+public:
+    CubicHermiteSplineFunctor() : _p0(), _m0(), _p1(), _m1() {}
+    CubicHermiteSplineFunctor(const glm::vec3& p0, const glm::vec3& m0, const glm::vec3& p1, const glm::vec3& m1) : _p0(p0), _m0(m0), _p1(p1), _m1(m1) {}
+
+    CubicHermiteSplineFunctor(const CubicHermiteSplineFunctor& orig) : _p0(orig._p0), _m0(orig._m0), _p1(orig._p1), _m1(orig._m1) {}
+
+    virtual ~CubicHermiteSplineFunctor() {}
+
+    // evalute the hermite curve at parameter t (0..1)
+    glm::vec3 operator()(float t) const {
+        float t2 = t * t;
+        float t3 = t2 * t;
+        float w0 = 2.0f * t3 - 3.0f * t2 + 1.0f;
+        float w1 = t3 - 2.0f * t2 + t;
+        float w2 = -2.0f * t3 + 3.0f * t2;
+        float w3 = t3 - t2;
+        return w0 * _p0 + w1 * _m0 + w2 * _p1 + w3 * _m1;
+    }
+
+    // evaulate the first derivative of the hermite curve at parameter t (0..1)
+    glm::vec3 d(float t) const {
+        float t2 = t * t;
+        float w0 = -6.0f * t + 6.0f * t2;
+        float w1 = 1.0f - 4.0f * t + 3.0f * t2;
+        float w2 = 6.0f * t - 6.0f * t2;
+        float w3 = -2.0f * t + 3.0f * t2;
+        return w0 * _p0 + w1 * _m0 + w2 * _p1 + w3 * _m1;
+    }
+
+    // evaulate the second derivative of the hermite curve at paramter t (0..1)
+    glm::vec3 d2(float t) const {
+        float w0 = -6.0f + 12.0f * t;
+        float w1 = -4.0f + 6.0f * t;
+        float w2 = 6.0f - 12.0f * t;
+        float w3 = -2.0f + 6.0f * t;
+        return w0 + _p0 + w1 * _m0 + w2 * _p1 + w3 * _m1;
+    }
+
+protected:
+    glm::vec3 _p0;
+    glm::vec3 _m0;
+    glm::vec3 _p1;
+    glm::vec3 _m1;
+};
+
+class CubicHermiteSplineFunctorWithArcLength : public CubicHermiteSplineFunctor {
+public:
+    enum Constants { NUM_SUBDIVISIONS = 30 };
+
+    CubicHermiteSplineFunctorWithArcLength() : CubicHermiteSplineFunctor() {
+        memset(_values, 0, sizeof(float) * (NUM_SUBDIVISIONS + 1));
+    }
+    CubicHermiteSplineFunctorWithArcLength(const glm::vec3& p0, const glm::vec3& m0, const glm::vec3& p1, const glm::vec3& m1) : CubicHermiteSplineFunctor(p0, m0, p1, m1) {
+        // initialize _values with the accumulated arcLength along the spline.
+        const float DELTA = 1.0f / NUM_SUBDIVISIONS;
+        float alpha = 0.0f;
+        float accum = 0.0f;
+        _values[0] = 0.0f;
+        for (int i = 1; i < NUM_SUBDIVISIONS + 1; i++) {
+            accum += glm::distance(this->operator()(alpha),
+                                   this->operator()(alpha + DELTA));
+            alpha += DELTA;
+            _values[i] = accum;
+        }
+    }
+
+    CubicHermiteSplineFunctorWithArcLength(const CubicHermiteSplineFunctorWithArcLength& orig) : CubicHermiteSplineFunctor(orig) {
+        memcpy(_values, orig._values, sizeof(float) * (NUM_SUBDIVISIONS + 1));
+    }
+
+    // given the spline parameter (0..1) output the arcLength of the spline up to that point.
+    float arcLength(float t) const {
+        float index = t * NUM_SUBDIVISIONS;
+        int prevIndex = std::min(std::max(0, (int)glm::floor(index)), (int)NUM_SUBDIVISIONS);
+        int nextIndex = std::min(std::max(0, (int)glm::ceil(index)), (int)NUM_SUBDIVISIONS);
+        float alpha = glm::fract(index);
+        return lerp(_values[prevIndex], _values[nextIndex], alpha);
+    }
+
+    // given an arcLength compute the spline parameter (0..1) that cooresponds to that arcLength.
+    float arcLengthInverse(float s) const {
+        // find first item in _values that is > s.
+        int nextIndex;
+        for (nextIndex = 0; nextIndex < NUM_SUBDIVISIONS; nextIndex++) {
+            if (_values[nextIndex] > s) {
+                break;
+            }
+        }
+        int prevIndex = std::min(std::max(0, nextIndex - 1), (int)NUM_SUBDIVISIONS);
+        float alpha = glm::clamp((s - _values[prevIndex]) / (_values[nextIndex] - _values[prevIndex]), 0.0f, 1.0f);
+        const float DELTA = 1.0f / NUM_SUBDIVISIONS;
+        return lerp(prevIndex * DELTA, nextIndex * DELTA, alpha);
+    }
+protected:
+    float _values[NUM_SUBDIVISIONS + 1];
+};
+
+#endif // hifi_CubicHermiteSpline_h
diff --git a/tests/shared/src/CubicHermiteSplineTests.cpp b/tests/shared/src/CubicHermiteSplineTests.cpp
new file mode 100644
index 0000000000..53a4ba8f6b
--- /dev/null
+++ b/tests/shared/src/CubicHermiteSplineTests.cpp
@@ -0,0 +1,77 @@
+//
+// CubicHermiteSplineTests.cpp
+// tests/shared/src
+//
+// Copyright 2017 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "CubicHermiteSplineTests.h"
+#include "../QTestExtensions.h"
+#include <QtCore/QDebug>
+#include "CubicHermiteSpline.h"
+
+QTEST_MAIN(CubicHermiteSplineTests)
+
+void CubicHermiteSplineTests::testCubicHermiteSplineFunctor() {
+    glm::vec3 p0(0.0f, 0.0f, 0.0f);
+    glm::vec3 m0(1.0f, 0.0f, 0.0f);
+    glm::vec3 p1(1.0f, 1.0f, 0.0f);
+    glm::vec3 m1(2.0f, 0.0f, 0.0f);
+    CubicHermiteSplineFunctor hermiteSpline(p0, m0, p1, m1);
+
+    const float EPSILON = 0.0001f;
+
+    QCOMPARE_WITH_ABS_ERROR(p0, hermiteSpline(0.0f), EPSILON);
+    QCOMPARE_WITH_ABS_ERROR(p1, hermiteSpline(1.0f), EPSILON);
+
+    // these values were computed offline.
+    const glm::vec3 oneFourth(0.203125f, 0.15625f, 0.0f);
+    const glm::vec3 oneHalf(0.375f, 0.5f, 0.0f);
+    const glm::vec3 threeFourths(0.609375f, 0.84375f, 0.0f);
+
+    QCOMPARE_WITH_ABS_ERROR(oneFourth, hermiteSpline(0.25f), EPSILON);
+    QCOMPARE_WITH_ABS_ERROR(oneHalf, hermiteSpline(0.5f), EPSILON);
+    QCOMPARE_WITH_ABS_ERROR(threeFourths, hermiteSpline(0.75f), EPSILON);
+}
+
+void CubicHermiteSplineTests::testCubicHermiteSplineFunctorWithArcLength() {
+    glm::vec3 p0(0.0f, 0.0f, 0.0f);
+    glm::vec3 m0(1.0f, 0.0f, 0.0f);
+    glm::vec3 p1(1.0f, 1.0f, 0.0f);
+    glm::vec3 m1(2.0f, 0.0f, 0.0f);
+    CubicHermiteSplineFunctorWithArcLength hermiteSpline(p0, m0, p1, m1);
+
+    const float EPSILON = 0.001f;
+
+    float arcLengths[5] = {
+        hermiteSpline.arcLength(0.0f),
+        hermiteSpline.arcLength(0.25f),
+        hermiteSpline.arcLength(0.5f),
+        hermiteSpline.arcLength(0.75f),
+        hermiteSpline.arcLength(1.0f)
+    };
+
+    // these values were computed offline
+    float referenceArcLengths[5] = {
+        0.0f,
+        0.268317f,
+        0.652788f,
+        1.07096f,
+        1.50267f
+    };
+
+    QCOMPARE_WITH_ABS_ERROR(arcLengths[0], referenceArcLengths[0], EPSILON);
+    QCOMPARE_WITH_ABS_ERROR(arcLengths[1], referenceArcLengths[1], EPSILON);
+    QCOMPARE_WITH_ABS_ERROR(arcLengths[2], referenceArcLengths[2], EPSILON);
+    QCOMPARE_WITH_ABS_ERROR(arcLengths[3], referenceArcLengths[3], EPSILON);
+    QCOMPARE_WITH_ABS_ERROR(arcLengths[4], referenceArcLengths[4], EPSILON);
+
+    QCOMPARE_WITH_ABS_ERROR(0.0f, hermiteSpline.arcLengthInverse(referenceArcLengths[0]), EPSILON);
+    QCOMPARE_WITH_ABS_ERROR(0.25f, hermiteSpline.arcLengthInverse(referenceArcLengths[1]), EPSILON);
+    QCOMPARE_WITH_ABS_ERROR(0.5f, hermiteSpline.arcLengthInverse(referenceArcLengths[2]), EPSILON);
+    QCOMPARE_WITH_ABS_ERROR(0.75f, hermiteSpline.arcLengthInverse(referenceArcLengths[3]), EPSILON);
+    QCOMPARE_WITH_ABS_ERROR(1.0f, hermiteSpline.arcLengthInverse(referenceArcLengths[4]), EPSILON);
+}
diff --git a/tests/shared/src/CubicHermiteSplineTests.h b/tests/shared/src/CubicHermiteSplineTests.h
new file mode 100644
index 0000000000..1828d1aa02
--- /dev/null
+++ b/tests/shared/src/CubicHermiteSplineTests.h
@@ -0,0 +1,23 @@
+//
+// CubicHermiteSplineTests.h
+// tests/shared/src
+//
+// Copyright 2017 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi_CubicHermiteSplineTests_h
+#define hifi_CubicHermiteSplineTests_h
+
+#include <QtTest/QtTest>
+
+class CubicHermiteSplineTests : public QObject {
+    Q_OBJECT
+private slots:
+    void testCubicHermiteSplineFunctor();
+    void testCubicHermiteSplineFunctorWithArcLength();
+};
+
+#endif // hifi_TransformTests_h