IK Chain

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.

There is some IK code in the Locomotion extension that might help you out.

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.

This seems to be a question that comes up once in a while, so I made an entry for it at UnityAnswers:

http://answers.unity3d.com/questions/3646/how-to-do-inverse-kinematics-ik-in-unity/3648#3648

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.

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 Law of cosines - Wikipedia

specifically I am going off the of the y = arcos function seen in the applications section

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

Here is the code so far.

private var rightFoot : Transform;
private var rightKnee : Transform;
private var rightHip : Transform;

var rightFootIK : Transform;

function OnDrawGizmos()
{

	rightFoot = transform.Find("greyParametric/CenterRoot/RightHip/RightKnee/RightFoot");
	rightKnee = transform.Find("greyParametric/CenterRoot/RightHip/RightKnee");
	rightHip = transform.Find("greyParametric/CenterRoot/RightHip");

	var hipToKnee : float = Vector3.Distance(rightHip.position, rightKnee.position);
	var kneeToFoot : float = Vector3.Distance(rightHip.position, rightKnee.position);
	var hipToFoot : float = Mathf.Clamp(Vector3.Distance(rightFootIK.position, rightHip.position), 0, hipToKnee + kneeToFoot); //total leng length
	var IKAngleFromHip : float = Mathf.Atan2(rightHip.position.x - rightFootIK.position.x, rightHip.position.y - rightFootIK.position.y);

	hipAngle = Mathf.Acos((hipToKnee * hipToKnee + hipToFoot * hipToFoot - kneeToFoot * kneeToFoot) / (2 * hipToKnee * hipToFoot));	
	kneeAngle = Mathf.Acos((kneeToFoot * kneeToFoot + hipToKnee * hipToKnee - hipToFoot * hipToFoot) / (2 * kneeToFoot * hipToKnee));

	rightKnee.eulerAngles.z =  ((kneeAngle  * -1 ) + IKAngleFromHip) * Mathf.Rad2Deg;	
	rightHip.eulerAngles.z = ((hipAngle  * -1 ) + IKAngleFromHip) * Mathf.Rad2Deg + 180;	

}

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.

In the script IK2JointAnalytic.cs there is this function:

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. :frowning:

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

Do you know what method IK2Joinanalytic is using to calculate the inverse kinematics? I cannot tell what its doing.

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.

public Vector3 findKnee(Vector3 pHip, Vector3 pAnkle, float fThigh, float fShin, Vector3 vKneeDir) {
	Vector3 vB = pAnkle-pHip;
	float LB = vB.magnitude;
	
	// ... Edge case handing here ...
	
	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);
}

260885--9373--$picture_1_602.png

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 :slight_smile:

They are using approximation of inverse sine cardinal function for IK.

function asinc(x0:float):float 
{
	var x :float = 6*(1-x0);	
	var x1:float = x;	
	var a :float = x;									        x*=x1;	
	a += x					 /20.0;						x*=x1;	
	a += x* 2.0				 /525.0;						x*=x1;	
	a += x* 13.0			 /37800.0;					x*=x1;	
	a += x* 4957.0			 /145530000.0;				x*=x1;	
	a += x* 58007.0			 /16216200000.0;			x*=x1;
	a += x* 1748431.0		 /4469590125000.0;			x*=x1;	
	a += x* 4058681.0		 /92100645000000.0;		x*=x1;
	a += x* 5313239803.0	 /1046241656460000000.0;	x*=x1;
	a += x* 2601229460539.0/4365681093774000000000.0;	// x^10
	return sqrt(a);
}


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

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 = (LBLB+fThighfThigh-fShinfShin)/2/LB;
float bb = Mathf.Sqrt(fThigh
fThigh-aaaa);
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.

private var rightFoot : Transform;
private var rightKnee : Transform;
private var rightHip : Transform;

var rightFootIK : Transform;

function OnDrawGizmos()
{

	rightFoot = transform.Find("greyParametric/CenterRoot/RightHip/RightKnee/RightFoot");
	rightKnee = transform.Find("greyParametric/CenterRoot/RightHip/RightKnee");
	rightHip = transform.Find("greyParametric/CenterRoot/RightHip");

	var hipToKnee : float = Vector3.Distance(rightHip.position, rightKnee.position);
	var kneeToFoot : float = Vector3.Distance(rightFoot.position, rightKnee.position);	
	var hipToFoot : float = Mathf.Clamp(Vector3.Distance(rightFootIK.position, rightHip.position), 0, hipToKnee + kneeToFoot); //total leg length
	var IKAngleFromHipZ : float = Mathf.Atan2(rightHip.position.x - rightFootIK.position.x, rightHip.position.y - rightFootIK.position.y) * Mathf.Rad2Deg;
	var IKAngleFromHipX : float = Mathf.Atan2(rightHip.position.x - rightFootIK.position.x, rightHip.position.y - rightFootIK.position.y) * Mathf.Rad2Deg;

	hipAngle = Mathf.Acos((hipToFoot/2)/hipToKnee)  * Mathf.Rad2Deg;	
	kneeAngle = Mathf.Asin((hipToFoot/2)/kneeToFoot) * Mathf.Rad2Deg;

	rightKnee.localEulerAngles.z =  90 -  kneeAngle + hipAngle;	
	rightHip.localEulerAngles.z = (180 - hipAngle) + IKAngleFromHipZ;	
	
}

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.

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:

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.

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.

It is because Solve is not a static function.

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

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

yeah… law of cosines is very useful and simple for finding the joins angle of 2-segmented IK :slight_smile:

function IK_2bone(a0:float, a1:float, mag:float):float[]
{		
	if (abs(a0-a1) > mag) { return [0.0, 180.0]; }
	if (abs(a0+a1) < mag) { return [0.0,   0.0]; }
	
	var x0 = CosLaw(mag, a0, a1);
	var x1 = CosLaw(a0, a1,  mag);		
	var t0 = (x0 -PI/2.0)*r2d;	
	var t1 = (x1 +PI/2.0)*r2d;
	
	return [t0, t1];
}

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)

function CosLaw(a:float, b:float, c:float):float
{
	return asin((a*a+b*b-c*c)/(2*a*b));
}

remark: this is my code style, I always overload the math function for easy viewing :stuck_out_tongue:

private var asin  = Mathf().Asin;
private var PI    = Mathf().PI;
private var r2d   = Mathf().Rad2Deg;
private var abs   = Mathf().Abs;

check this out. I leant from there :slight_smile: many many more related information out there too.
http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/index.htm

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?

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 :slight_smile:

   //---- parent joins    
   t[0].z += atan2(target_vec.y, target_vec.x)*r2d; 
   if(join[0].parent) 
   { 
      t[0].z -= join[0].parent.eulerAngles.z; 
   }

yeah… as of the above code, after find the 3D angle, minus it with the parent join angle :slight_smile:

This is the full script for your reference.
(remark, this script should apply on the “child” bone)

var reference:GameObject;

var bone_0		:float  = 1;
var bone_1		:float  = 1;

var join_bias	:float 	 = 0;

private var target:GameObject;

function IK_2bone(a0:float, a1:float, mag:float):float[]
{		
	if (abs(a0-a1) > mag) { return [0.0, 180.0]; }
	if (abs(a0+a1) < mag) { return [0.0,   0.0]; }
	
	//var a	= a0+a1;
	var b 	= mag;
	var x0 	= CosLaw( b, a0, a1);
	var x1 	= CosLaw(a0, a1,  b);		
	var t0 = (x0 -PI/2.0)*r2d;	
	var t1 = (x1 +PI/2.0)*r2d;
	
	return [t0, t1];
}

function FixedUpdate () 
{
	if(reference  target)
	{
		target.transform.position = reference.transform.position;
	}
	//----- setup joins	
	var L:int 		= 2;					
	var join 		= new Array();
		join[L-1] 	= this.transform;
	var i:int;
	for(i=L-1;i>0;i--) 
	{ 
		join[i-1] = join[i].parent; 
	}
	//----- setup target
	if (!target) 
	{	
		target 							= new GameObject(); 
		target.name 					= "_target";
		target.transform.position 		= Vector3.zero;
		target.transform.parent 		= this.transform;
		target.transform.localPosition	= Vector3(bone_1, 0, 0);
	}
	//----- calc vector
	var base_pos 	=   join[0].transform.position;
	var target_pos 	=  target.transform.position;
	var target_vec  = (target_pos - base_pos).normalized;
	var target_mag	= (target_pos - base_pos).magnitude;	
	//---- init
	var t = new Vector3[L];
	for(i=0;i<L;i++) 
	{ 
		t[i] = Vector3.zero; 
	}
	//----- body joins
	var sector_angle = new float[L];
	sector_angle = IK_2bone(bone_0, bone_1, target_mag);

	var dotL = Vector3.Dot(target_vec,join[L-1].transform.up); 	
	for(i=0;i<L;i++) 
	{ 
		t[i].z 	  -= sector_angle[i]*sign(dotL +join_bias);
	}
	//---- parent joins	
	t[0].z += atan2(target_vec.y, target_vec.x)*r2d;
	if(join[0].parent)
	{
		t[0].z -= join[0].parent.eulerAngles.z;
	}
	//---- assign
	target.transform.parent = null;		
	for(i=0;i<L;i++)
	{ 		
		join[i].Rotate(t[i]-join[i].localEulerAngles);
	}	
	target.transform.parent = this.transform;
	_pline(target_pos, base_pos, 6);
}

//-------------------- helper ------------------------------
function OnDrawGizmos() 
{
	if(!target) return;
	Gizmos.color = Color(1,1,0);
    Gizmos.DrawCube (target.transform.position,  Vector3.one*0.05);
}

private static var TP_str: String = "...";

function OnGUI() { GUI.Label(Rect(0,0,320,240), TP_str); }

function CosLaw(a:float, b:float, c:float):float
{
	return asin((a*a+b*b-c*c)/(2*a*b));
}

function _ray(p:Vector3, v:Vector3, c:Color) { Debug().DrawRay(p,v,c); }
function _pline(p1:Vector3, p2:Vector3, c:int)
{	
	var b = c % 2; c /=2;
	var g = c % 2; c /=2;
	var r = c % 2;
	_line(p1, p2, Color(r, g, b));
}

private var _log  = Debug().Log;
private var _line = Debug().DrawLine;
private var atan2 = Mathf().Atan2;
private var asin   = Mathf().Asin;
private var PI 	  = Mathf().PI;
private var r2d   = Mathf().Rad2Deg;
private var sqrt  = Mathf().Sqrt;
private var sign  = Mathf().Sign;
private var abs   = Mathf().Abs;

(once again, most of the math and debug function are overloaded :p)

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 :slight_smile:

Enjoy!

263448–9489–$iksimplechain_254.cs (5.87 KB)
263448–9490–$tuberenderer_106.js (5.65 KB)