Search Unity

IK Chain

Discussion in 'Scripting' started by techmage, Feb 3, 2010.

  1. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    I am embarking on trying to make an IK Chain solver in Unityscript. I don't really need help yet, but I wanted to ask, has anyone done this and would you be willing to share your script?

    But I do have one question about doing this.
    Of course this is going to rely heavily on reading and writing rotation values. I am wondering, would doing this in quaternions offer any performance benefit over using EulerAngles? Because I have no idea how a quaternion works and am inclined to use euler angles. But if euler angles cause a performance hit I'll figure out quaternions.
     
  2. andeeeee

    andeeeee

    Joined:
    Jul 19, 2005
    Posts:
    8,768
  3. Vimalakirti

    Vimalakirti

    Joined:
    Oct 12, 2009
    Posts:
    755
    I've taken a look at that locomotion system and it's an incredible piece of work, but still way over my head.

    I'm working on procedural animations right now, too (see scripting thread "Can you read this book?") but on a more basic level. IK would work much better for me, but I still don't understand how eulers work well enough yet to write an IK script.
     
  4. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
  5. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    Quaternions are a bit faster, but the main reason for using them is that you get smoother interpolation of angles.

    With euler angles, sometimes you will get weird rotations if you try to interpolate between two sets of angles.

    I've read quite a few research papers related to animation, and in the recent ten years or so they all use quaternions rather than euler angles because it has just proven to work better.
     
  6. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    I looked in the locomotion system but can't really follow it, and I need to understand it thoroughly in this application.

    I am trying to implement a 2 bone solve based on the law of cosines, read about here http://en.wikipedia.org/wiki/Cosine_formula

    specifically I am going off the of the y = arcos function seen in the applications section
    http://en.wikipedia.org/wiki/Cosine_formula#Applications


    But I CANNOT get this to work. It seems so simple but it just won't do it.

    Here is the code so far.

    Code (csharp):
    1.  
    2. private var rightFoot : Transform;
    3. private var rightKnee : Transform;
    4. private var rightHip : Transform;
    5.  
    6. var rightFootIK : Transform;
    7.  
    8. function OnDrawGizmos()
    9. {
    10.  
    11.     rightFoot = transform.Find("greyParametric/CenterRoot/RightHip/RightKnee/RightFoot");
    12.     rightKnee = transform.Find("greyParametric/CenterRoot/RightHip/RightKnee");
    13.     rightHip = transform.Find("greyParametric/CenterRoot/RightHip");
    14.  
    15.     var hipToKnee : float = Vector3.Distance(rightHip.position, rightKnee.position);
    16.     var kneeToFoot : float = Vector3.Distance(rightHip.position, rightKnee.position);
    17.     var hipToFoot : float = Mathf.Clamp(Vector3.Distance(rightFootIK.position, rightHip.position), 0, hipToKnee + kneeToFoot); //total leng length
    18.     var IKAngleFromHip : float = Mathf.Atan2(rightHip.position.x - rightFootIK.position.x, rightHip.position.y - rightFootIK.position.y);
    19.  
    20.     hipAngle = Mathf.Acos((hipToKnee * hipToKnee + hipToFoot * hipToFoot - kneeToFoot * kneeToFoot) / (2 * hipToKnee * hipToFoot));
    21.     kneeAngle = Mathf.Acos((kneeToFoot * kneeToFoot + hipToKnee * hipToKnee - hipToFoot * hipToFoot) / (2 * kneeToFoot * hipToKnee));
    22.  
    23.     rightKnee.eulerAngles.z =  ((kneeAngle  * -1 ) + IKAngleFromHip) * Mathf.Rad2Deg;  
    24.     rightHip.eulerAngles.z = ((hipAngle  * -1 ) + IKAngleFromHip) * Mathf.Rad2Deg + 180;   
    25.  
    26. }
    27.  
    How that works is, you have to set rightFoot, rightKnee and rightHip to your foot, knee and hip joints respectively. Through the transform.Find functions

    Then you make a seperate cube and drag it onto the rightFootIK transform slot.

    The leg then follows the rightFootIK cube.

    This HALF-way works. You can fully extend the leg, and it will aim at the cube correctly. As you move the cube closer to the hip, the leg will retract and fold up but it retracts faster than you move the cube!

    And I cannot figure this out, seriously have been staring at this for liek 12 hours. I cannot figure out why it doesn't work. I'm thinking unity might be producing a slightly different acos value then what'd be expected.
     
  7. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    In the script IK2JointAnalytic.cs there is this function:

    Code (csharp):
    1. public override void Solve(Transform[] bones, Vector3 target)
    The bones array should contain the hip, knee, and ankle transforms, and the target is where the ankle should be. The function will then modify the transforms using IK.

    Sorry, I don't have time to look into your code. :(

    No, that is not the case. Mathf.Acos is based on the Math.Acos function in Mono. The error must be somewhere else.
     
  8. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    Do you know what method IK2Joinanalytic is using to calculate the inverse kinematics? I cannot tell what its doing.
     
  9. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    The interesting part that finds the position of the knee is the code below. I can't remember all the details, but it's basic trigonometry. The attached image may be helpful if you want to investigate further.

    Code (csharp):
    1. public Vector3 findKnee(Vector3 pHip, Vector3 pAnkle, float fThigh, float fShin, Vector3 vKneeDir) {
    2.     Vector3 vB = pAnkle-pHip;
    3.     float LB = vB.magnitude;
    4.    
    5.     // ... Edge case handing here ...
    6.    
    7.     float aa = (LB*LB+fThigh*fThigh-fShin*fShin)/2/LB;
    8.     float bb = Mathf.Sqrt(fThigh*fThigh-aa*aa);
    9.     Vector3 vF = Vector3.Cross(vB,Vector3.Cross(vKneeDir,vB));
    10.     return pHip+(aa*vB.normalized)+(bb*vF.normalized);
    11. }
     

    Attached Files:

  10. apple_motion

    apple_motion

    Joined:
    Jul 2, 2009
    Posts:
    169
    yeah... I am study and try to make my version of full body IK. (still in the research stage). Maybe we can share the idea and code someday later :)

    http://www.youtube.com/watch?v=UNrrd_XhPMA
    http://www.youtube.com/watch?v=S-zAk6VqL-E
    They are using approximation of inverse sine cardinal function for IK.

    Code (csharp):
    1. function asinc(x0:float):float
    2. {
    3.     var x :float = 6*(1-x0);   
    4.     var x1:float = x;  
    5.     var a :float = x;                                           x*=x1; 
    6.     a += x                   /20.0;                     x*=x1; 
    7.     a += x* 2.0              /525.0;                        x*=x1; 
    8.     a += x* 13.0             /37800.0;                  x*=x1; 
    9.     a += x* 4957.0           /145530000.0;              x*=x1; 
    10.     a += x* 58007.0          /16216200000.0;            x*=x1;
    11.     a += x* 1748431.0        /4469590125000.0;          x*=x1; 
    12.     a += x* 4058681.0        /92100645000000.0;     x*=x1;
    13.     a += x* 5313239803.0     /1046241656460000000.0;    x*=x1;
    14.     a += x* 2601229460539.0/4365681093774000000000.0;   // x^10
    15.     return sqrt(a);
    16. }

    a, the length of arc from base to target.
    b, the distance of base to target
     
  11. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    Thanks for your guys help.

    runevision, I was trying to use your IK solver as a public class by going

    IKSolver.IK2JointAnalytic.Solve(rightLeg,rightFootIK);
    but that doesn't work. I'm trying to call it from javascript. Is it possible to call c# functions from js?

    Also about the code

    float aa = (LB*LB+fThigh*fThigh-fShin*fShin)/2/LB;
    float bb = Mathf.Sqrt(fThigh*fThigh-aa*aa);
    Vector3 vF = Vector3.Cross(vB,Vector3.Cross(vKneeDir,vB));
    return pHip+(aa*vB.normalized)+(bb*vF.normalized)

    I can see the float bb = is using the Pythagorean theorem. But what is float aa = doing? Is there a name for whats going on there?

    and this right here
    Vector3 vF = Vector3.Cross(vB,Vector3.Cross(vKneeDir,vB));

    I assume vKneeDir would point in the same direction as vF on the diagram? Is that correct? Or would it point down the thigh? But doing that set of cross products seems like it just cancels itself out? What is that doing exaclty?



    I still kind of want to get an IK solver that I can fully comprehend running and so I've been plugging at the code and I managed to get a 2 bone analytic solver working through a different trig function, just a simple acos and asin call to get angles of a right right triangle.

    Code (csharp):
    1.  
    2. private var rightFoot : Transform;
    3. private var rightKnee : Transform;
    4. private var rightHip : Transform;
    5.  
    6. var rightFootIK : Transform;
    7.  
    8. function OnDrawGizmos()
    9. {
    10.  
    11.     rightFoot = transform.Find("greyParametric/CenterRoot/RightHip/RightKnee/RightFoot");
    12.     rightKnee = transform.Find("greyParametric/CenterRoot/RightHip/RightKnee");
    13.     rightHip = transform.Find("greyParametric/CenterRoot/RightHip");
    14.  
    15.     var hipToKnee : float = Vector3.Distance(rightHip.position, rightKnee.position);
    16.     var kneeToFoot : float = Vector3.Distance(rightFoot.position, rightKnee.position); 
    17.     var hipToFoot : float = Mathf.Clamp(Vector3.Distance(rightFootIK.position, rightHip.position), 0, hipToKnee + kneeToFoot); //total leg length
    18.     var IKAngleFromHipZ : float = Mathf.Atan2(rightHip.position.x - rightFootIK.position.x, rightHip.position.y - rightFootIK.position.y) * Mathf.Rad2Deg;
    19.     var IKAngleFromHipX : float = Mathf.Atan2(rightHip.position.x - rightFootIK.position.x, rightHip.position.y - rightFootIK.position.y) * Mathf.Rad2Deg;
    20.  
    21.     hipAngle = Mathf.Acos((hipToFoot/2)/hipToKnee)  * Mathf.Rad2Deg;   
    22.     kneeAngle = Mathf.Asin((hipToFoot/2)/kneeToFoot) * Mathf.Rad2Deg;
    23.  
    24.     rightKnee.localEulerAngles.z =  90 -  kneeAngle + hipAngle;
    25.     rightHip.localEulerAngles.z = (180 - hipAngle) + IKAngleFromHipZ;  
    26.    
    27. }
    That only works if both the shin and the thigh are the same length, and is essentially a 2D solver. And I'm having a helluva a time trying to figure out how to get it into a 3D solver.

    I think I have a way figured out but, I can't figure out how to do one thing. I need to essentially parent a joint to a transform in code but not actually parent it in the hiearchy. I know you can do this with fixed joints but I would like to keep rigidbodies off my skeleton. Does anyone know how to parent a transform to another transform, all in script? Without actually making it a parent in the hiearchy? So like you would rotate the parent transform in it's local angles, and it would add that rotation to another transform as if it were a child.
     
  12. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    The function takes an array of Transforms, not just one Transform.

    You can call C# functions from JavaScript if you make sure the C# script is compiled before the JavaScript script that is calling it. See this for more info on controlling the order of compilation:
    http://unity3d.com/support/documentation/ScriptReference/index.Script_compilation_28Advanced29.html

    I don't know 100% because I based the script on formulas I found elsewhere, but it seems to be derived from the law of cosines.

    vKneeDir should be the general direction the knee should be pointing in. vF is just a corrected version of vKneeDir that is forced to be perpendicular to vB, in case vKneeDir was not perpendicular to vB.
     
  13. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    I put IKSolver and IK2JointAnalytic into a folder called "Plugins" but when I try IKSolver.Solve([rightHip,rightKnee,rightFoot],rightFootIK);

    it still says BCE0020: An instance of type 'IKSolver' is required to access non static member 'Solve'.

    I don't understand why that would be required.

    In the locomotion example project those scripts are just in "\Assets\Locomotion System", which I assume places them in the 4th compile group. But they still work.
     
  14. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    It is because Solve is not a static function.

    That is because they are called from other c# scripts, not from Javascript.
     
  15. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    Ohh. I see.

    So I just tried to edit your script into being a static function and somehow managed to completely break unity. It froze, then crashed. And now when I open it, it's unresponsive and says 'IsD3D9DeviceLost() || g_d3dInsideScene'

    :?

    I rebooted and now it says FMOD failed to initialize

    I guess I gotta reinstall

    ..


    Help. I reinstalled unity and it still opens with an error!?!

    It's not saying anything in the console now, just giving an error bleep and freezing...

    ...

    ok I found it was just something in 1 scene...

    I wonder what caused that
     
  16. apple_motion

    apple_motion

    Joined:
    Jul 2, 2009
    Posts:
    169
    yeah... law of cosines is very useful and simple for finding the joins angle of 2-segmented IK :)

    Code (csharp):
    1. function IK_2bone(a0:float, a1:float, mag:float):float[]
    2. {      
    3.     if (abs(a0-a1) > mag) { return [0.0, 180.0]; }
    4.     if (abs(a0+a1) < mag) { return [0.0,   0.0]; }
    5.    
    6.     var x0 = CosLaw(mag, a0, a1);
    7.     var x1 = CosLaw(a0, a1,  mag);     
    8.     var t0 = (x0 -PI/2.0)*r2d; 
    9.     var t1 = (x1 +PI/2.0)*r2d;
    10.    
    11.     return [t0, t1];
    12. }
    13.  
    input:
    a0, length of parent segment
    a1, length of child segment
    mag, the distance between "base" "target" point

    output:
    t0, the angle at base join.
    t1, the angle between segments



    (to find the opposite angle of side c from 3 given length)
    Code (csharp):
    1. function CosLaw(a:float, b:float, c:float):float
    2. {
    3.     return asin((a*a+b*b-c*c)/(2*a*b));
    4. }
    5.  
    remark: this is my code style, I always overload the math function for easy viewing :p
    Code (csharp):
    1. private var asin  = Mathf().Asin;
    2. private var PI    = Mathf().PI;
    3. private var r2d   = Mathf().Rad2Deg;
    4. private var abs   = Mathf().Abs;
     
  17. apple_motion

    apple_motion

    Joined:
    Jul 2, 2009
    Posts:
    169
  18. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    that works awesomely apple_motion

    One question though, have you put any thought into getting that method to work in 3D?

    Thats currently where I was at using my implementation, and also now yours. How do you get X and Y rotation values onto the hipjoint so that it points at the IK target no matter where it is.

    My current thought was, the X and Y rotation values can't be applied directly to the hipjoint. Because it's not the hipJoint that has to point at the IK target, but rather the non-existing vector between the hip joint and the foot joint that has to point at the IK target.

    So there needs to be some kind of imaginary transform at the hip, that aims at the IK target, and then the real hip transform needs to be parented to this imaginary transform. But that needs to be done in code... and I cannot figure out how to parent one objects rotations to another purely in code. Any ideas?
     
  19. apple_motion

    apple_motion

    Joined:
    Jul 2, 2009
    Posts:
    169
    Not yet for this implementation, since I jumped to working on the Bézier Curve version, but as you see on the script, it is 3D ready, most of variables are using vector3. On the following portion of code, simply change the "atan2" to some others find 3D angle method that should make it work on 3D too :)
    Code (csharp):
    1.    //---- parent joins    
    2.    t[0].z += atan2(target_vec.y, target_vec.x)*r2d;
    3.    if(join[0].parent)
    4.    {
    5.       t[0].z -= join[0].parent.eulerAngles.z;
    6.    }
    7.  
    yeah... as of the above code, after find the 3D angle, minus it with the parent join angle :)

    This is the full script for your reference.
    (remark, this script should apply on the "child" bone)
    Code (csharp):
    1. var reference:GameObject;
    2.  
    3. var bone_0      :float  = 1;
    4. var bone_1      :float  = 1;
    5.  
    6. var join_bias   :float   = 0;
    7.  
    8. private var target:GameObject;
    9.  
    10. function IK_2bone(a0:float, a1:float, mag:float):float[]
    11. {      
    12.     if (abs(a0-a1) > mag) { return [0.0, 180.0]; }
    13.     if (abs(a0+a1) < mag) { return [0.0,   0.0]; }
    14.    
    15.     //var a = a0+a1;
    16.     var b   = mag;
    17.     var x0  = CosLaw( b, a0, a1);
    18.     var x1  = CosLaw(a0, a1,  b);      
    19.     var t0 = (x0 -PI/2.0)*r2d; 
    20.     var t1 = (x1 +PI/2.0)*r2d;
    21.    
    22.     return [t0, t1];
    23. }
    24.  
    25. function FixedUpdate ()
    26. {
    27.     if(reference  target)
    28.     {
    29.         target.transform.position = reference.transform.position;
    30.     }
    31.     //----- setup joins
    32.     var L:int       = 2;                   
    33.     var join        = new Array();
    34.         join[L-1]   = this.transform;
    35.     var i:int;
    36.     for(i=L-1;i>0;i--)
    37.     {
    38.         join[i-1] = join[i].parent;
    39.     }
    40.     //----- setup target
    41.     if (!target)
    42.     {  
    43.         target                          = new GameObject();
    44.         target.name                     = "_target";
    45.         target.transform.position       = Vector3.zero;
    46.         target.transform.parent         = this.transform;
    47.         target.transform.localPosition  = Vector3(bone_1, 0, 0);
    48.     }
    49.     //----- calc vector
    50.     var base_pos    =   join[0].transform.position;
    51.     var target_pos  =  target.transform.position;
    52.     var target_vec  = (target_pos - base_pos).normalized;
    53.     var target_mag  = (target_pos - base_pos).magnitude;   
    54.     //---- init
    55.     var t = new Vector3[L];
    56.     for(i=0;i<L;i++)
    57.     {
    58.         t[i] = Vector3.zero;
    59.     }
    60.     //----- body joins
    61.     var sector_angle = new float[L];
    62.     sector_angle = IK_2bone(bone_0, bone_1, target_mag);
    63.  
    64.     var dotL = Vector3.Dot(target_vec,join[L-1].transform.up);  
    65.     for(i=0;i<L;i++)
    66.     {
    67.         t[i].z    -= sector_angle[i]*sign(dotL +join_bias);
    68.     }
    69.     //---- parent joins
    70.     t[0].z += atan2(target_vec.y, target_vec.x)*r2d;
    71.     if(join[0].parent)
    72.     {
    73.         t[0].z -= join[0].parent.eulerAngles.z;
    74.     }
    75.     //---- assign
    76.     target.transform.parent = null;    
    77.     for(i=0;i<L;i++)
    78.     {      
    79.         join[i].Rotate(t[i]-join[i].localEulerAngles);
    80.     }  
    81.     target.transform.parent = this.transform;
    82.     _pline(target_pos, base_pos, 6);
    83. }
    84.  
    85. //-------------------- helper ------------------------------
    86. function OnDrawGizmos()
    87. {
    88.     if(!target) return;
    89.     Gizmos.color = Color(1,1,0);
    90.     Gizmos.DrawCube (target.transform.position,  Vector3.one*0.05);
    91. }
    92.  
    93. private static var TP_str: String = "...";
    94.  
    95. function OnGUI() { GUI.Label(Rect(0,0,320,240), TP_str); }
    96.  
    97. function CosLaw(a:float, b:float, c:float):float
    98. {
    99.     return asin((a*a+b*b-c*c)/(2*a*b));
    100. }
    101.  
    102. function _ray(p:Vector3, v:Vector3, c:Color) { Debug().DrawRay(p,v,c); }
    103. function _pline(p1:Vector3, p2:Vector3, c:int)
    104. {  
    105.     var b = c % 2; c /=2;
    106.     var g = c % 2; c /=2;
    107.     var r = c % 2;
    108.     _line(p1, p2, Color(r, g, b));
    109. }
    110.  
    111. private var _log  = Debug().Log;
    112. private var _line = Debug().DrawLine;
    113. private var atan2 = Mathf().Atan2;
    114. private var asin   = Mathf().Asin;
    115. private var PI    = Mathf().PI;
    116. private var r2d   = Mathf().Rad2Deg;
    117. private var sqrt  = Mathf().Sqrt;
    118. private var sign  = Mathf().Sign;
    119. private var abs   = Mathf().Abs;
    120.  
    (once again, most of the math and debug function are overloaded :p)
     
  20. slgooding

    slgooding

    Joined:
    Jan 12, 2009
    Posts:
    112
    I modified the IKSimple script from the locomotion tutorial so that you could drop it on a root node and add an ik handle.

    1) Create a hierarchy of joints
    2) Create a GameObject (iKHandle)
    3) Drop script onto root joint for IK
    4) Assign iKHandle to the script
    5) Run and move the iKHandle or the Root Joint

    I don't have nearly as much control as I would like. It would be great if someone could add code for weighting of the "Joints".

    I've also modified the TubeRenderer script to work with this so that you can easily see what is going on. Great for creating wires/tubes, etc. Simply drop the script onto the root joint along with the other script.

    Hopefully this is helpful. Again, it would be great if someone could modify this to support bone weights :)

    Enjoy!
     

    Attached Files:

  21. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    This is all incredibly helpful to me, and I'm sure alot of others too, thanks all of you.

    slgooding, from looking at this script, if I understand correctly, is the method used in iksimplechain a CCD method? Or runevision, since I guess this is your script and you'd know better, is it a CCD method? Did you end up using a CCD IK method on the locomotion system implementation? I was thinking about trying to do this myself but worried about speed. Does the CCD method take a whole lot more computation that it'd be something to worry about in performance terms?

    Although I can't get this to work reliably on a whole skeleton, it'll properly solve for a bit until moved out of range, then the knee will lock and the foot will start to go wonky.

    applemotion, now it makes sense why I was struggling so hard... for some reason I thought I needed to apply the X and Y rotations AFTER the Z rotation... :roll: anyways. that bezier solver looks quite good.


    Anyone know what kind of math goes into doing a pole vector constraint? Or a rotate plane? I'm trying to figure out how to get control over the direction the knee points.
     
  22. slgooding

    slgooding

    Joined:
    Jan 12, 2009
    Posts:
    112
    To be honest I don't understand a lot of the underlying code. I simply modified the original ik script from the locomotion tutorial. Wish I could be of more help, and I should probably read into it as I would like to add weighting to the joints.

    There are definitely some performance issues that need to be considered with this script and it seems to be "jittery" when moving the ik handle along certain axis. This may just be a matter of a simple modification to that code. However this doesn't seem to be the case if you transform the root joint though ( and it make work all the same if you treat that as the ik handle ).

    The max iterations can be reduced to improve performance. Depending on the scenario you can drop it significantly (down to 15 or something around that). The iterations is basically determines how many times it can loop before it finds the optimal joint rotations and positions based on the previous positions of each joint.
     
  23. apple_motion

    apple_motion

    Joined:
    Jul 2, 2009
    Posts:
    169
    did you mean the unwanted flipping after the join angle is zero (or the bones are in the straight line) ?

    If that is the case, simply set the "join_bias" other than 0 will solve the problem. This make the join will only allowed to rotate in one direction not both.
    Code (csharp):
    1. var dotL = Vector3.Dot(target_vec,join[L-1].transform.up);    
    2. for(i=0;i<L;i++)
    3. {
    4.     t[i].z -= sector_angle[i]*sign(dotL +join_bias);
    5. }
    6.  
    (remark: as mentioned, that is tested on 2D situation, you may need to modify it for 3D, however, the concept should be very similar :)
     
  24. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    I ran into one odd thing when converting it to 3D. Just want to make sure I'm thinking about this correctly.

    Let me try to illustrate this in ascii

    Code (csharp):
    1.  
    2. H
    3. |\
    4. | \
    5. |  \
    6. |   K
    7. |  /
    8. | /
    9. |/
    10. F
    11.  

    So that is your triangle for solving IK, H = Hip, K = Knee, F = Foot.

    I have it set up so rotations around the Z axis is forward and back on the leg, X axis is left and right.

    Now the thing is, when doing the X axis rotation, you cannot directly rotate the hip joint on the X axis. Because the X axis rotation needs to be performed on a joint that is aimed at the foot. But the hip joint is aimed at the knee, so it would orient it wrong. When doing the x axis rotation it is the vector from the hip to the foot that has to be rotated on the x axis, not the vector from the hip to the knee. But the actual hip joint is aimed at the knee, so you gotta do something about that.

    I was thinking that you could apply the X axis rotation BEFORE the z axis rotation, because before the Z axis rotation the hip is aimed at the foot. But this doesn't seem possible in anyway. Whether you apply the z axis or x axis rotation first in code makes no difference.

    So right now I am using an additional quaternion at the hip joint, that is aimed at the foot, then I rotate that quaternion on the X axis, the actual hip joints rotation is multiplied by the additional hip quaternions rotation (so it acts like a child of the additional hip quaternion). And the only rotation applied to the actual hip joint is on the Z axis. Then the X and Y axis rotations on the hip are done through the additional hip quaternion that remains aimed at the foot.

    This is working.... I can't think of how you would do this any other way, you have to have an additional quaternion at the hip joint. Unless I'm wrong here? Am I? Thats why I typed all that out.

    But anyways. My hip joint DOES need to rotate on all axis, I can't remove rotation from one of the axis. I just need to get some kind of control on all the axis. I need to figure a way to get the additional hip quaternion to aim at both the foot, and another IK target in order to get the knee to point the proper direction, and be able to control where the knee points. Essentialy a pole vector is what Maya calls them. Any ideas on how to do that?

    Here is the code I have appended onto yours, I'm currently doing this all in OnDrawGizmos just cause it is easier to test, but will move it to Update when finished.

    Now I have it set up so that rightFoot, rightKnee and rightHip transforms are set through the find function. And also as I said before, Z rotation rotates the leg forward and back, X rotation rotates it left and right, Y rotation rotates where the knee aims.


    Code (csharp):
    1. #pragma strict
    2.  
    3. //http://www.thesixtyone.com/ochremusic/song/Vegas/j8kRl3cfwm1/
    4.  
    5. private var asin  = Mathf().Asin;
    6. private var PI    = Mathf().PI;
    7. private var r2d   = Mathf().Rad2Deg;
    8. private var abs   = Mathf().Abs;
    9.  
    10.  
    11. function CosLaw(a:float, b:float, c:float):float
    12. {
    13.    return asin((a*a+b*b-c*c)/(2*a*b));
    14. }
    15.  
    16.  
    17. function IK_2bone(a0:float, a1:float, mag:float):float[]
    18. {      
    19.    if (abs(a0-a1) > mag) { return [0.0, 180.0]; }
    20.    if (abs(a0+a1) < mag) { return [0.0,   0.0]; }
    21.    
    22.    var x0 = CosLaw(mag, a0, a1);
    23.    var x1 = CosLaw(a0, a1,  mag);      
    24.    var t0 = (x0 -PI/2.0)*r2d;    
    25.    var t1 = (x1 +PI/2.0)*r2d;
    26.    
    27.    return [t0, t1];
    28. }
    29.  
    30. private var rightFoot : Transform;
    31. private var rightKnee : Transform;
    32. private var rightHip : Transform;
    33.  
    34. var rightFootIK : Transform;
    35.  
    36. function OnDrawGizmos()
    37. {
    38.    
    39.     rightFoot = transform.Find("greyParametric/CenterRoot/RightHip/RightKnee/RightFoot");
    40.     rightKnee = transform.Find("greyParametric/CenterRoot/RightHip/RightKnee");
    41.     rightHip = transform.Find("greyParametric/CenterRoot/RightHip");
    42.  
    43.     var hipToKneeQuat : Quaternion;
    44.     var hipToFootQuat : Quaternion;
    45.  
    46.     var hipToKnee : float = Vector3.Distance(rightHip.position, rightKnee.position);
    47.     var kneeToFoot : float = Vector3.Distance(rightKnee.position, rightFoot.position);
    48.     //var hipToFoot : float = Mathf.Clamp(Vector3.Distance(rightFootIK.position, rightHip.position), 0, hipToKnee + kneeToFoot); //total leg length clamped
    49.     var hipToFoot : float = Vector3.Distance(rightFootIK.position, rightHip.position); //total leg length
    50.    
    51.     var IKAngleFromHipZ : float = Mathf.Atan2(rightFootIK.position.y - rightHip.position.y, rightFootIK.position.x - rightHip.position.x) * Mathf.Rad2Deg;
    52.     var IKAngleFromHipX : float = Mathf.Atan2(rightHip.position.y - rightFootIK.position.y,  rightHip.position.z - rightFootIK.position.z) * Mathf.Rad2Deg;
    53.  
    54.     var legAngles  : float[] = IK_2bone(hipToKnee, kneeToFoot, hipToFoot);
    55.    
    56.     hipToFootQuat.eulerAngles.y = 180;  //where the knee aims
    57.     hipToFootQuat.eulerAngles.z = 90 - IKAngleFromHipZ;
    58.     hipToFootQuat.eulerAngles.x = IKAngleFromHipX - 90;
    59.    
    60.     rightKnee.localEulerAngles.z = legAngles[1];   
    61.        
    62.     hipToKneeQuat.eulerAngles = Vector3( 0, 0, legAngles[0]);
    63.     rightHip.rotation = hipToFootQuat * hipToKneeQuat; //quaternion multiplication will make hipToKneeQuat a child to the rotations of hipToFootQuat
    64.    
    65. }

    If anyone has any ideas on how to get this knee constrained, ideally like a pole vector in maya, I'd like to hear.


    Also apple_motion, you said that you were aiming for a full body IK solution? I am actually aiming for the same thing as well. I'm curious, what are you intending to use it for? I myself am attempting to create a parametric kung fu animation system for a sidescroller.
     
  25. apple_motion

    apple_motion

    Joined:
    Jul 2, 2009
    Posts:
    169
    In fact, we are doing the same thing but for different purpose :) my goal is build a full physics based ballet simulator.
     
  26. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    I was thinking about this alot yesterday, but don't you think a CCD solution would be most ideal for full body IK? I'm pretty sure thats what the autodesk humanIK middleware uses. It's a CCD solver that will solve the entire skeleton.
     
  27. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    I GOT THE POLE VECTOR WORKING

    thanks to the quaternion look at function

    put a transform for rightKneeIK, and the knee will aim at it! Even at extremes, or above and below the waist.

    It works just like a maya pole vector



    Code (csharp):
    1. #pragma strict
    2.  
    3. private var rightFoot : Transform;
    4. private var rightKnee : Transform;
    5. private var rightHip : Transform;
    6.  
    7. var rightFootIK : Transform;
    8. var rightKneeIK : Transform;
    9.  
    10. private var asin  = Mathf().Asin;
    11. private var PI    = Mathf().PI;
    12. private var r2d   = Mathf().Rad2Deg;
    13. private var abs   = Mathf().Abs;
    14.  
    15. function CosLaw(a:float, b:float, c:float):float
    16. {
    17.    return asin((a*a+b*b-c*c)/(2*a*b));
    18. }
    19.  
    20. function IK_2bone(a0:float, a1:float, mag:float):float[]
    21. {      
    22.    if (abs(a0-a1) > mag) { return [0.0, 180.0]; }
    23.    if (abs(a0+a1) < mag) { return [0.0,   0.0]; }
    24.    
    25.    var x0 = CosLaw(mag, a0, a1);
    26.    var x1 = CosLaw(a0, a1,  mag);      
    27.    var t0 = (x0 -PI/2.0)*r2d;    
    28.    var t1 = (x1 +PI/2.0)*r2d;
    29.    
    30.    return [t0, t1];
    31. }
    32.  
    33. function OnDrawGizmos()
    34. {
    35.  
    36.     rightFoot = transform.Find("greyParametric/CenterRoot/RightHip/RightKnee/RightFoot");
    37.     rightKnee = transform.Find("greyParametric/CenterRoot/RightHip/RightKnee");
    38.     rightHip = transform.Find("greyParametric/CenterRoot/RightHip");
    39.  
    40.     var hipToKneeQuat : Quaternion;
    41.     var hipToFootQuat : Quaternion;
    42.  
    43.     var hipToKnee : float = Vector3.Distance(rightHip.position, rightKnee.position);
    44.     var kneeToFoot : float = Vector3.Distance(rightKnee.position, rightFoot.position);
    45.     var hipToFoot : float = Vector3.Distance(rightFootIK.position, rightHip.position); //total leg length
    46.    
    47.     var legAngles  : float[] = IK_2bone(hipToKnee, kneeToFoot, hipToFoot);
    48.    
    49.     hipToFootQuat.SetLookRotation(rightFootIK.position - rightHip.position, rightKneeIK.position - rightHip.position);
    50.    
    51.     rightKnee.localEulerAngles.z = legAngles[1];   
    52.        
    53.     hipToKneeQuat.eulerAngles = Vector3(0, -270, legAngles[0] + 90);//will probably need to adjust depending on your skeleton
    54.     rightHip.rotation = hipToFootQuat * hipToKneeQuat; //quaternion multiplication will make hipToKneeQuat a child to the rotations of hipToFootQuat
    55.    
    56. }
    57.  
    [/img]
     
  28. apple_motion

    apple_motion

    Joined:
    Jul 2, 2009
    Posts:
    169
    well done ! your script is better than my mine :) so that, I could copy your version later. (hope you don't mine :p)
     
  29. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    Half that script is your script...
     
  30. MitchStan

    MitchStan

    Joined:
    Feb 26, 2007
    Posts:
    568
    This is a great thread guys - great read, great work!
     
  31. apple_motion

    apple_motion

    Joined:
    Jul 2, 2009
    Posts:
    169
    I use LookAt function too :) that help my version go 3D ! as expected, only few lines of code need to change :p

    Code (csharp):
    1. join[0].rotation = lookAt_Quat(target_vec, Vector3.right);
    2. join[1].Rotate(t[1]-join[1].localEulerAngles);
    3. join[0].Rotate(t[0]);
    4.  
    Code (csharp):
    1. function lookAt_Quat(v1:Vector3, v0:Vector3):Quaternion  // v0 -> v1
    2. {
    3.     v0 = v0.normalized;
    4.     v1 = v1.normalized;
    5.     var n = cross(v0, v1);
    6.     n = n.normalized;
    7.     var t:float = acos(dot(v0, v1))/2.0;   
    8.     return Quaternion(sin(t)*n.x, sin(t)*n.y, sin(t)*n.z, cos(t));
    9. }
    (Remark: The up vector from the output of the above function is not same as the unity build-in lookAt.)

    Of course, I can use build-in lookAt too, but I need to do additional 90 degree rotation, since my setup is not use "forward" as lookAt position :p
    Code (csharp):
    1. var qR = Quaternion(0, -sin(PI/2), 0, sin(PI/2));
    2. var q0:Quaternion;
    3. q0.SetLookRotation(target_vec, Vector3.up);
    4. join[0].rotation = q0*qR;
     
  32. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    apple_motion, have you seen this?

    http://www.youtube.com/watch?v=X5Z7ZJ39zAA
    http://grail.cs.washington.edu/projects/styleik/

    seems to be the ultimate full body IK solution

    but potentially very diffucult and costly to implement...

    hm it could probably be done though

    I am going to continue to try to come up with something off of an analytical solution first though.

    Trying to figure out how to position the root hip joint, and how much to rotate the root hip joint, so that if you move the footIK the character looks like hes still standing in balance.
     
  33. apple_motion

    apple_motion

    Joined:
    Jul 2, 2009
    Posts:
    169
    no, that is too new to me. I still study from the basic concept, especially on the robotics. (since, I have electronic engineering background, maybe one day I could make a robot not just a simulator :p)

    16 lectures of "Introduction to Robotics" from Stanford University,
    http://www.youtube.com/watch?v=0yD3uBshJB0
    these courses are very informative, full of interesting, and up-today material, except the mathematic model of robotic is little hard to me. but that is the most important part of the lecture. I shall make it :)
     
  34. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    I took the law of cosines script and condensed it as short as possible, with the least amount of calls and variables and made it so that you can drop it on anything.

    If you use maya, the script is set up to work with joints oriented to XYZ with second axis world orientation +Y, you set this via the 'Orient Joint Options' menu.

    Otherwise you will need to edit a couple of the variable to make it orient properly

    Code (csharp):
    1. var Root : Transform;
    2. var Mid : Transform;
    3. var End : Transform;
    4.  
    5. var MidIK : Transform;
    6. var EndIK : Transform; 
    7.    
    8. function LateUpdate()
    9. {  
    10.     var RootToMid : float = Vector3.Distance(Root.position, Mid.position);
    11.     var MidToEnd : float = Vector3.Distance(Mid.position, End.position);
    12.     var RootToEnd : float = Vector3.Distance(EndIK.position, Root.position); //total leg length
    13.    
    14.     var RootToMidQuat : Quaternion;
    15.     var RootToEndQuat : Quaternion;    
    16.    
    17.     RootToEndQuat.SetLookRotation(EndIK.position - Root.position, MidIK.position - Root.position);  
    18.    
    19.     if (Mathf.Abs(RootToMid-MidToEnd) > RootToEnd)
    20.     {      
    21.         Mid.localEulerAngles.z = 180;          
    22.         RootToMidQuat.eulerAngles = Vector3(0, -270, 0); //-270 determines direction Knee will face
    23.     }  
    24.     else if (Mathf.Abs(RootToMid+MidToEnd) < RootToEnd)
    25.     {              
    26.         Mid.localEulerAngles.z = 0;        
    27.         RootToMidQuat.eulerAngles = Vector3(0, -270, 0);   
    28.     }
    29.     else
    30.     {          
    31.         //uses pythagorean theroem to figure out angles
    32.         Mid.localEulerAngles.z = Mathf.Asin((RootToMid*RootToMid+MidToEnd*MidToEnd-RootToEnd*RootToEnd)/(2*RootToMid*MidToEnd))*Mathf.Rad2Deg + 90;            
    33.         RootToMidQuat.eulerAngles = Vector3(0, -270, Mathf.Asin((RootToEnd*RootToEnd+RootToMid*RootToMid-MidToEnd*MidToEnd)/(2*RootToEnd*RootToMid))*Mathf.Rad2Deg - 90);
    34.     }  
    35.     Root.rotation = RootToEndQuat * RootToMidQuat; //quaternion multiplication will make RootToMidQuat a child to the rotations of RootToEndQuat       
    36.     End.rotation = EndIK.rotation; 
    37. }
    I got a question about this though.

    If I take that lateUpdate function and also make it a OnDrawGizmos function to see the IK work in the viewport without playing, will this cause any sort of clash or take up unneeded cpu cycles once exported to a web app or standalone?
     
  35. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,824
    Hey I just found this thread, I've been trying (without success) to get a simple IK chain going for a while but this thread saved me!

    I attached a little example scene with a rigged up set of legs, rigged witth eem's script.



    Thanks!
    Pete
     

    Attached Files:

    Last edited: Jan 6, 2011
    ZJP likes this.
  36. ninthjarl

    ninthjarl

    Joined:
    Jan 13, 2011
    Posts:
    3
    Brilliant explanation runevision. Thank you. Now I understand the code better.
     
  37. Sock Puppet

    Sock Puppet

    Joined:
    Jan 9, 2011
    Posts:
    77
    Cool thread some useful stuff here.

    I've started implementing an fairly recent IK method called FABRIK which seems promising in comparison with CCD, Jacobian methods etc.

    You can read the paper here - www.andreasaristidou.com/publications/FABRIK.pdf
    ..and video here http://www.andreasaristidou.com/FABRIK.html

    I've done the easy bit in converting the main algorithm but need to figure out an elegant way of implementing multi end effectors and constraints.
    Here's the package, just move the orange sphere around in the editor view while unity is running.

    http://dl.dropbox.com/u/8521226/FABRIK.unitypackage
     
  38. tzvier

    tzvier

    Joined:
    May 20, 2010
    Posts:
    355
    The script that techmage posted works great for my project, except that my character's knee bends backwards, and I can't seem to figure out why. Any folks out there smarter than I know how I can fix this?

    Edit: Solved this, had to make some adjustments to the rig's bone rotations. Working well now.
     
    Last edited: Oct 11, 2012
  39. kukamed

    kukamed

    Joined:
    Jan 23, 2013
    Posts:
    14
    I've converted the FABRIK main algorithm too. And couldn't understand how the constraints are done. It´s a bit confusing on the article. Have you managed to do constraints?

    BTW, Your link is broken!
     
  40. bradowado

    bradowado

    Joined:
    Aug 19, 2012
    Posts:
    9
    Cool stuff. Does anyone know how to make this work when the joints are oriented to the world? Also, wanted to make a correction to what techmage says regarding joint orientation for his script: it should actually read "joints oriented to XYZ with second axis world orientation Z +". To do this in Maya:
    1. Select the root joint
    2. Edit > Select Hierarchy
    3. Display > Transform Display > Local Rotation Axes (this is optional, it just shows you the joint pivots)
    4. Animation Mode
    5. Skeleton > Orient Joints (Option Box)
    6. Orient Joint Options:
    - Edit > Reset Settings
    - Primary Axis = X
    - Secondary Axis = Y
    - Secondary Axis World Orientation = Z +
     
  41. ZJP

    ZJP

    Joined:
    Jan 22, 2010
    Posts:
    2,649
    I know it's an old thread but it's save me a lot of time. So, here a Csharp version of the script
    Code (csharp):
    1.  
    2.  
    3. using UnityEngine;
    4. using System.Collections;
    5.  
    6. public class Iktest : MonoBehaviour
    7. {
    8.    public Transform Root;
    9.    public Transform Mid;
    10.    public Transform End;
    11.  
    12.    public Transform MidIK;
    13.    public Transform EndIK;  
    14.  
    15.    Quaternion RootToMidQuat = Quaternion.identity;
    16.    Quaternion RootToEndQuat = Quaternion.identity;  
    17.  
    18.    void  LateUpdate ()
    19.    {  
    20.      float RootToMid = Vector3.Distance(Root.position, Mid.position);
    21.      float MidToEnd  = Vector3.Distance(Mid.position, End.position);
    22.      float RootToEnd = Vector3.Distance(EndIK.position, Root.position); //total leg length  
    23.  
    24.      RootToEndQuat.SetLookRotation(EndIK.position - Root.position, MidIK.position - Root.position);  
    25.    
    26.      if (Mathf.Abs(RootToMid-MidToEnd) > RootToEnd)
    27.      {    
    28.        Mid.localEulerAngles =  new Vector3(Mid.localEulerAngles.x, Mid.localEulerAngles.y, 180);      
    29.        RootToMidQuat.eulerAngles = new Vector3(0, -270, 0); //-270 determines direction Knee will face
    30.      }  
    31.      else if (Mathf.Abs(RootToMid+MidToEnd) < RootToEnd)
    32.      {        
    33.        Mid.localEulerAngles =  new Vector3(Mid.localEulerAngles.x, Mid.localEulerAngles.y, 0);      
    34.        RootToMidQuat.eulerAngles = new Vector3(0, -270, 0);  
    35.      }
    36.      else
    37.      {      
    38.        //uses pythagorean theroem to figure out angles
    39.        float modz = Mathf.Asin((RootToMid*RootToMid+MidToEnd*MidToEnd-RootToEnd*RootToEnd)/(2*RootToMid*MidToEnd))*Mathf.Rad2Deg + 90;        
    40.        Mid.localEulerAngles = new Vector3(Mid.localEulerAngles.x, Mid.localEulerAngles.y, modz);        
    41.        RootToMidQuat.eulerAngles = new Vector3(0, -270, Mathf.Asin((RootToEnd*RootToEnd+RootToMid*RootToMid-MidToEnd*MidToEnd)/(2*RootToEnd*RootToMid))*Mathf.Rad2Deg - 90);
    42.      }  
    43.      Root.rotation = RootToEndQuat * RootToMidQuat; //quaternion multiplication will make RootToMidQuat a child to the rotations of RootToEndQuat      
    44.      End.rotation = EndIK.rotation;  
    45.    }
    46. }
    47.  
     
    Deyama and Ryiah like this.
  42. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,824
    Hey nice one!
     
  43. ZJP

    ZJP

    Joined:
    Jan 22, 2010
    Posts:
    2,649
    Used to build my own foot placement system in a 'Gravity like Mario-Galaxy' project. The main perso is a multi parts robot. Perfect... :cool:
     
  44. Deyama

    Deyama

    Joined:
    Apr 18, 2014
    Posts:
    6
    Sorry for resurrecting an old thread, but could you please tell how to make it work for arms? As it is, it works for the left arm, but the right arm acts as if something's inversed / flipped / mirrored
     
  45. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,824
    Hey Deyama,
    I delved back into this script a little while ago and had some painful issues with flipping and stuff.
    If you can, I would definitely check out this plugin - https://www.assetstore.unity3d.com/en/#!/content/14290
    The dev is great and its super easy to get cool IK stuff happening. It'll save you a bunch of time (and headaches).
    P.
     
  46. Deyama

    Deyama

    Joined:
    Apr 18, 2014
    Posts:
    6
    Yeah, it surely would. Apart from it being a bit on the expensive side for me at the moment. But I probably will try it some day in the future.

    For now I figured out that it was that "-270" in
    that flips the thing. So need to change this for left and right arms or probably come up with some formula that automates the change. And there's still some similar issue with the elbow target
     
  47. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,824
    Hey nice one! I guess you could just put a little checkbox to flip that value when needed in the script.
    Yeah Final ik is a bit pricey. I guess it's because this kind of ik is only a small part of the plugins capabilities.

    P.
     
  48. ZJP

    ZJP

    Joined:
    Jan 22, 2010
    Posts:
    2,649
    Bio IK. 20$ on the Store.