Saturday night sketch: FK+IK Simultaneous Chain

By August 20, 2011 September 23rd, 2013 No Comments

To prove the technique described on the last post I built a little thing: a couple of bones with FK and IK behaviour at the same time. It’s just a quick sketch based on one of the videos from ToonKit. It could be developed further to make it animation friendly since it’s a rough prototype. But let me explain you how I did this…

One of the videos from ToonKit features this behaviour, and from what it’s shown I reckon the IK control is purely used to drive the rotation of the bones. Well, this is how an IK system actually behaves. But rather than controlling/overwriting the rotation of the bones by using an IK Solver, we could do the whole calculations ‘by hand’ and then tell the EulerXYZ controller of both bones (just the Z Rotation in this case) how many degrees they should rotate.

After doing some research, I found a complete mathematical explanation on how an IK system works here. As one might expect, everything is based on trigonometry. I really recommend you to follow Ryan Juckett’s explanations here to try to understand… which I barely did since I wanted to finish this sketch before going to bed, so I went straight to the code he provides and translated it to MAXScript. I know, I’m going to hell for this :-)

/// CalcIK_2D_TwoBoneAnalytic
/// Given a two bone chain located at the origin (bone1 is the parent of bone2), this
/// function will compute the bone angles needed for the end of the chain to line up
/// with a target position. If there is no valid solution, the angles will be set to
/// get as close to the target as possible.
/// returns: True when a valid solution was found.

fn clampValue value upperLimit lowerLimit =
	if value > upperLimit then return upperLimit else
		if value < lowerLimit then return lowerLimit else
			return value

fn CalcIK_2D_TwoBoneAnalytic length1 length2 targetX targetY=
	-- variables
	local angle1 -- angle of bone 1
	local angle2 -- angle of bone 2
	local solvePosAngle2 = true -- solve for positive angle 2 instead of negative angle 2
	-- local length1 -- length of bone 1
	-- local length2 -- length of bone 2
	-- local targetX -- target x position for the bones to reach
	-- local targetY -- target y position for the bones to reach

	local epsilon = 0.0001 -- used to prevent division by small numbers
	local foundValidSolution = true
	local targetDistSqr = (targetX*targetX + targetY*targetY)

   -- Compute a new value for angle2 along with its cosine
    local sinAngle2
    local cosAngle2

    local cosAngle2_denom = 2*length1*length2;
    if cosAngle2_denom > epsilon then
        cosAngle2 =  (targetDistSqr - length1*length1 - length2*length2)/(cosAngle2_denom)

        -- if our result is not in the legal cosine range, we can not find a legal solution for the target
        if( (cosAngle2 < -1.0) or (cosAngle2 > 1.0) ) do foundValidSolution = false

        -- clamp our value into range so we can calculate the best solution when there are no valid ones
        cosAngle2 = clampValue cosAngle2 1 -1

        --compute a new value for angle2
        angle2 = acos cosAngle2

        -- adjust for the desired bend direction
        if solvePosAngle2 do
            angle2 = -angle2

        -- compute the sine of our angle
        sinAngle2 = Sin angle2
        --At leaset one of the bones had a zero length. This means our
        --solvable domain is a circle around the origin with a radius
        --equal to the sum of our bone lengths.
        local totalLenSqr = (length1 + length2) * (length1 + length2)
        if targetDistSqr < (totalLenSqr-epsilon) or targetDistSqr > (totalLenSqr+epsilon) do foundValidSolution = false

        -- Only the value of angle1 matters at this point. We can just set angle2 to zero.
        -- angle2    = 0.0;
        -- cosAngle2 = 1.0;
        -- sinAngle2 = 0.0;

    -- Compute the value of angle1 based on the sine and cosine of angle2
    local triAdjacent = length1 + length2*cosAngle2
    local triOpposite = length2*sinAngle2

    local tanY = targetY*triAdjacent - targetX*triOpposite
    local tanX = targetX*triAdjacent + targetY*triOpposite

    -- Note that it is safe to call Atan2(0,0) which will happen if targetX and targetY are zero
    angle1 = atan2 tanY tanX
	return #(angle1, angle2)
   --return foundValidSolution

values = CalcIK_2D_TwoBoneAnalytic $Bone01.length $Bone02.length $Point01.pos.x -$Point01.pos.z

if selection[1] == GetNodeByName("Point01") then
	$Bone01.rotation.controller[1].controller.y_rotation = values[1]
	$Bone02.rotation.controller[1].controller.y_rotation = values[2]
	$Point01.position = $Bone03.position
	$Point01.position = $Bone03.position
	-- $Bone01.rotation.controller[1].controller.y_rotation = values[1]
	-- $Bone02.rotation.controller[1].controller.y_rotation = values[2]

You can try this code on a scene with 2 bones and 1 point. Each time you execute it the whole thing will align. So we need something to run it one time and another and another… without evaluating it on the MAXScript Editor. So now it’s the time to use the info provided on the previous post. Create another object (the red point on the video), throw a Script Controller on some property you do not plan to use (Box), and paste that code, leaving the 0 as the last line. And voilĂ !