Skip to content
This repository has been archived by the owner on Jul 30, 2019. It is now read-only.

Commit

Permalink
Add stretchy bone IK component
Browse files Browse the repository at this point in the history
- Includes midpoint pinning
- Includes IK/FK blending
- Includes midpoint sliding
- Includes soft IK (both stretchy and non-stretchy)
  • Loading branch information
BigRoy committed Feb 11, 2016
1 parent 8744f9e commit 420346f
Show file tree
Hide file tree
Showing 3 changed files with 672 additions and 0 deletions.
1 change: 1 addition & 0 deletions Exts/Kraken/Kraken.fpm.json
Expand Up @@ -10,6 +10,7 @@
"Solvers/KrakenMultiPoseConstraintSolver.kl"
"Solvers/BezierSpineSolver.kl",
"Solvers/TwoBoneIKSolver.kl",
"Solvers/TwoBoneStretchyIKSolver.kl"
"Solvers/NBoneIKSolver.kl",
"Solvers/TentacleSolver.kl",
"Solvers/DynamicChainSolver.kl",
Expand Down
267 changes: 267 additions & 0 deletions Exts/Kraken/Solvers/TwoBoneStretchyIKSolver.kl
@@ -0,0 +1,267 @@
require InlineDrawing;
require Kraken;
require Math;
require Animation;


object TwoBoneStretchyIKSolver : KrakenSolver {
Xfo initPose[];
};


// Default Constructor
function TwoBoneStretchyIKSolver()
{

}


function TwoBoneStretchyIKSolver(
Xfo initPose[])
{
this.initPose = initPose;
}


// Return Arguments for Kraken
function KrakenSolverArg[] TwoBoneStretchyIKSolver.getArguments(){
KrakenSolverArg args[] = this.parent.getArguments();
args.push(KrakenSolverArg('rightSide', 'In', 'Boolean'));

args.push(KrakenSolverArg('ikblend', 'In', 'Scalar'));
args.push(KrakenSolverArg('softIK', 'In', 'Boolean'));
args.push(KrakenSolverArg('softRatio', 'In', 'Scalar'));
args.push(KrakenSolverArg('stretch', 'In', 'Boolean'));
args.push(KrakenSolverArg('stretchBlend', 'In', 'Scalar'));
args.push(KrakenSolverArg('slide', 'In', 'Scalar'));
args.push(KrakenSolverArg('pin', 'In', 'Scalar'));

args.push(KrakenSolverArg('root', 'In', 'Mat44'));
args.push(KrakenSolverArg('bone0FK', 'In', 'Mat44'));
args.push(KrakenSolverArg('bone1FK', 'In', 'Mat44'));
args.push(KrakenSolverArg('ikHandle', 'In', 'Mat44'));
args.push(KrakenSolverArg('upV', 'In', 'Mat44'));

args.push(KrakenSolverArg('bone0Len', 'In', 'Scalar'));
args.push(KrakenSolverArg('bone1Len', 'In', 'Scalar'));
args.push(KrakenSolverArg('bone0Out', 'Out', 'Mat44'));
args.push(KrakenSolverArg('bone1Out', 'Out', 'Mat44'));
args.push(KrakenSolverArg('bone2Out', 'Out', 'Mat44'));
return args;
}


// Solve
function TwoBoneStretchyIKSolver.solve!
(
Boolean drawDebug,
Scalar rigScale,
Boolean rightSide,

Scalar ikblend,
Boolean softIK,
Scalar softRatio,
Boolean stretch,
Scalar stretchBlend,
Scalar slide,
Scalar pin,

Mat44 root,
Mat44 bone0FK,
Mat44 bone1FK,
Mat44 ikHandle,
Mat44 upV,

Scalar bone0Len,
Scalar bone1Len,
io Mat44 bone0Out,
io Mat44 bone1Out,
io Mat44 bone2Out
){

Xfo bone0FkXfo = Xfo(bone0FK);
Xfo bone1FkXfo = Xfo(bone1FK);

Xfo bone0Xfo;
Xfo bone1Xfo;
Xfo bone2Xfo;

// Scale to global rig scale
Scalar scaledBone0Len = bone0Len * rigScale;
Scalar scaledBone1Len = bone1Len * rigScale;

// Solve IK
if(ikblend > 0.0)
{

Vec3 ikHandlePos = ikHandle.translation();
Vec3 rootPos = root.translation();
Scalar distanceToIK = (rootPos - ikHandlePos).length();

// Lock mid to the upVector (pole vector)
// e.g. could be used to lock an elbow on a table
if (pin > 0.0)
{
Vec3 pinPt = upV.translation();
scaledBone0Len = Math_linearInterpolate(scaledBone0Len, (pinPt - rootPos).length(), pin);
scaledBone1Len = Math_linearInterpolate(scaledBone1Len, (pinPt - ikHandlePos).length(), pin);
}

if (pin != 1.0)
{

// TODO: Allow scale of zero and the evaluation to finish
Scalar chainLen = scaledBone0Len + scaledBone1Len;
//if (chainLen == 0) return;

// Slide mid to end (+1) or to start (-1)
if (slide != 0.0)
{
Scalar shift;
if (slide > 0.0)
shift = slide * scaledBone1Len;
else
shift = slide * scaledBone0Len;

// Fix pinning blend (when 0 < pin < 1)
if (pin > 0.0) shift = Math_linearInterpolate(shift, 0.0, pin);

// Update the bone lengths
scaledBone0Len += shift;
scaledBone1Len -= shift;
chainLen = scaledBone0Len + scaledBone1Len;
}

// Soft IK scale
Scalar finalRatio = 1.0;
if (softIK && softRatio > 0.0)
{
Scalar softLen = softRatio * chainLen;
Scalar hardLen = chainLen - softLen;

// If we left the hardDistArea we entered the soft area
if (distanceToIK > hardLen)
{
Scalar exponent = -(distanceToIK - hardLen) / softLen;
Scalar softTargetDistance = softLen * (1 - exp(exponent)) + hardLen;

finalRatio = softTargetDistance / distanceToIK;

// Fix pinning blend (when 0 < pin < 1)
if (pin > 0.0) finalRatio = Math_linearInterpolate(finalRatio, 1.0, pin);

if (!stretch || stretchBlend < 1.0)
{
// For this we drag the IK handle behind after the softDist
// See: http://www.softimageblog.com/archives/108
// Compute soft IK location for non-stretchy
Scalar nonStretchyFinalRatio = Math_linearInterpolate(finalRatio, 1.0, stretchBlend);
Vec3 direction = ikHandlePos - rootPos;
direction = direction.multiplyScalar(nonStretchyFinalRatio);
ikHandlePos = rootPos + direction;
distanceToIK *= nonStretchyFinalRatio;
}

if (stretch && stretchBlend > 0.0)
{
// Scale bones by ratio between IK and Soft IK so that we preserve the soft IK
// motion while hitting the IK handle. See: http://www.softimageblog.com/archives/109
Scalar stretchyFinalRatio = 1.0 / Math_linearInterpolate(1.0, finalRatio, stretchBlend);

chainLen *= stretchyFinalRatio;
scaledBone0Len *= stretchyFinalRatio;
scaledBone1Len *= stretchyFinalRatio;
}

}
}

// Stretchy
if (stretch && stretchBlend > 0.0)
{
if (chainLen < distanceToIK)
{
Scalar diff = distanceToIK / chainLen;
diff = Math_linearInterpolate(1.0, diff, stretchBlend);
scaledBone0Len *= diff;
scaledBone1Len *= diff;
}

}
}

solve2BoneIK(
scaledBone0Len,
scaledBone1Len,
root.translation(),
upV.translation(),
ikHandlePos,
bone0Xfo,
bone1Xfo
);

// Project bone2 to the end of bone1
bone2Xfo = bone1Xfo;
bone2Xfo.tr = bone1Xfo.transformVector(Vec3(scaledBone1Len, 0.0, 0.0));

// Set IK bone scaling
bone0Xfo.sc = Vec3(scaledBone0Len / bone0Len, rigScale, rigScale);

This comment has been minimized.

Copy link
@BigRoy

BigRoy Feb 17, 2016

Author Owner

Note that when the IK bone "stretches" beyond its initial length it receives a "scale" change in that direction as opposed to solely a translation change.

This is something that I've seen discussed here and there about "you should do that!" and "you should not do that!" so I'm kind of in the middle about what's preferred. ;)

So just mentioning it here so it's been raised and could possibly be discussed. With linear skinning scaling gives a tiny bit better result (afaik) whereas some dual quaternion implementations might mess that up (not sure)?

bone1Xfo.sc = Vec3(scaledBone1Len / bone1Len, rigScale, rigScale);
bone2Xfo.sc = Vec3(rigScale, rigScale, rigScale);

}

// Solve FK
if (ikblend < 1.0)
{
// Project bone2 to the end of bone1
Xfo bone2FkXfo = bone1FkXfo;
bone2FkXfo.tr = bone1FkXfo.transformVector(Vec3(bone1Len, 0.0, 0.0));

// Set FK scale
bone0FkXfo.sc = bone0FkXfo.sc.multiplyScalar(rigScale);
bone1FkXfo.sc = bone1FkXfo.sc.multiplyScalar(rigScale);
bone2FkXfo.sc = bone2FkXfo.sc.multiplyScalar(rigScale);

// Only FK
if (ikblend == 0.0)
{
bone0Xfo = bone0FkXfo;
bone1Xfo = bone1FkXfo;
bone2Xfo = bone2FkXfo;
}
// Solve IK/FK blend
else
{
// Orient and scale the bone and then position the child bone based on the parent's length
// We don't use the Xfo.transformVector() so we don't get the "scaled" transform value along
// with the length of the bone that already contains that "scale". Otherwise we'd have double
// transformation.
bone0Xfo.ori = bone0FkXfo.ori.sphericalLinearInterpolate(bone0Xfo.ori, ikblend);
bone0Xfo.sc = bone0FkXfo.sc.linearInterpolate(bone0Xfo.sc, ikblend);

Scalar bone0OutLen = Math_linearInterpolate(bone0FkXfo.sc.x * bone0Len * rigScale, scaledBone0Len, ikblend);
bone1Xfo.tr = bone0Xfo.tr + bone0Xfo.ori.rotateVector(Vec3(bone0OutLen, 0, 0));
bone1Xfo.ori = bone1FkXfo.ori.sphericalLinearInterpolate(bone1Xfo.ori, ikblend);
bone1Xfo.sc = bone1FkXfo.sc.linearInterpolate(bone1Xfo.sc, ikblend);

Scalar bone1OutLen = Math_linearInterpolate(bone1FkXfo.sc.x * bone1Len * rigScale, scaledBone1Len, ikblend);
bone2Xfo.tr = bone1Xfo.tr + bone1Xfo.ori.rotateVector(Vec3(bone1OutLen, 0, 0));
bone2Xfo.ori = bone2FkXfo.ori.sphericalLinearInterpolate(bone2Xfo.ori, ikblend);
bone2Xfo.sc = bone2FkXfo.sc.linearInterpolate(bone2Xfo.sc, ikblend);
}
}

bone0Out = bone0Xfo.toMat44();
bone1Out = bone1Xfo.toMat44();
bone2Out = bone2Xfo.toMat44();

// Set debugging visibility.
this.setDebug(drawDebug);
if(this.drawDebug){

Color boneColor(1.0, 1.0, 0);
etDrawBone(this.handle.rootTransform, 'bone0', bone0Xfo, scaledBone0Len, scaledBone0Len * 0.15, boneColor);
etDrawBone(this.handle.rootTransform, 'bone1', bone1Xfo, scaledBone1Len, scaledBone1Len * 0.15, boneColor);
}
}

0 comments on commit 420346f

Please sign in to comment.