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 ) else ( --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 == GetNodeByName("Point01") then ( $Bone01.rotation.controller.controller.y_rotation = values $Bone02.rotation.controller.controller.y_rotation = values $Point01.position = $Bone03.position ) else ( $Point01.position = $Bone03.position -- $Bone01.rotation.controller.controller.y_rotation = values -- $Bone02.rotation.controller.controller.y_rotation = values )
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à!