DEV Community

David
David

Posted on

Inverse Kinematics Solver using the Fabrik method

Want to learn how to implement an Inverse Kinematics(Ik) solver in Miniscript? In this post I'll teach you how.

Final Result:

Ik is one of the coolest things in programming. It can make animations look more realistic and can help achieving complex behaviors from simple rules. In this tutorial we'll be using a method called Fabrik which stands for “Forward And Backward Reaching Inverse Kinematics”. We are going to make a robot arm that tries to reach the mouse. Let's code!

Setup

Boot up MiniMicro, make a folder called “ik” with the command: mkdir “ik” and hit enter, navigate the folder by typing: cd “ik”. Note that the argument past to the command has to be between quotation marks. Now let's create our MiniScript file with: save “main”, and start editing by typing edit.

Starting Code

We'll start by clearing the screen with: clear. Now we define some variables, distance is the distance between joints, and points is a list in which we store joints so we can loop through them easily. The current code should look like this:

clear distance = 64 points = [] 
Enter fullscreen mode Exit fullscreen mode

Now let's create a custom class that extend the Sprite to add a handy function and avoid repeating complex code. Also let's load a basic image to see the points, and scale it up a little.

Download this sample image, rename it to "point.png" and place it next to your "main.ms" file.

Point = new Sprite Point.image = file.loadImage("point.png") Point.scale = 6 Point.normalize = function length = sqrt(self.x * self.x + self.y * self.y) if length != 0 then self.x /= length self.y /= length else self.x = 0 self.y = 0 end if end function 
Enter fullscreen mode Exit fullscreen mode

Don't worry if you don't understand this code, just know that it converts the x and y values to arrange between 0 and 1. We make use of the Pythagorean theorem.

Ok, so now we add 3 items to the ‘points’ list. For that we use a basic for loop.

for i in range(2) p = new Point display(4).sprites.push p points.push p end for 
Enter fullscreen mode Exit fullscreen mode

And finally, let's add a variable named anchor which will be the anchor point of the arm, let's set it to the center of the screen, and dir, which will store the direction from one point no another.

anchor = new Point anchor.x = 480 anchor.y = 320 dir = new Point 
Enter fullscreen mode Exit fullscreen mode

The whole script should look like this:

clear distance = 64 points = [] Point = new Sprite Point.image = file.loadImage("bone.png") Point.scale = 6 Point.normalize = function length = sqrt(self.x * self.x + self.y * self.y) if length != 0 then self.x /= length self.y /= length else self.x = 0 self.y = 0 end if end function for i in range(2) p = new Point display(4).sprites.push p points.push p end for anchor = new Point anchor.x = 480 anchor.y = 320 dir = new Point 
Enter fullscreen mode Exit fullscreen mode

The Fun Part

Now that we got everything ready it's time to code the “ik” solver. Let's start a while loop and clear the screen to a white color.

while true gfx.clear color.white // Next will go here... yield end while 
Enter fullscreen mode Exit fullscreen mode

A quick detail, the Fabrik method is done in two steps. We start from the last point, move it to the target position and then adjust all other points accordingly. In the second step we start from the first point, move it to the origin position and adjust all other points. That's why this method is called Fabrik, because we move forward and backwards, in the process, all points move to the best possible position in which they don't overlap, and the distance between each position is the one we desired.

With that said, let's code. First let's move all the points towards the target position, in our case, the coordinates of the mouse. Let's set the last point (the tip of the arm) to mouse position.

// forward iteration points[points.len - 1].x = mouse.x points[points.len - 1].y = mouse.y 
Enter fullscreen mode Exit fullscreen mode

Now we loop through the points left, starting from the penultimate. We calculate the direction to the next point and adjust the position of the current point using the direction multiplied by the length and subtracting that vector to the next point position.

for i in range(points.len-2, 0) dir.x = points[i + 1].x - points[i].x dir.y = points[i + 1].y - points[i].y dir.normalize points[i].x = points[i + 1].x - dir.x * distance points[i].y = points[i + 1].y - dir.y * distance end for 
Enter fullscreen mode Exit fullscreen mode

The last step is very similar to the previous one, except we go in reverse. Let's set the first point (the arm anchor) to the origin position.

//backwards iteration points[0].x = anchor.x points[0].y = anchor.y 
Enter fullscreen mode Exit fullscreen mode

Now we loop again but skipping the first point since we already set it's position. We calculate the direction, this time with the previous point and adjust it's position.

for i in range(1, points.len - 1) dir.x = points[i - 1].x - points[i].x dir.y = points[i - 1].y - points[i].y dir.normalize points[i].x = points[i - 1].x - dir.x * distance points[i].y = points[i - 1].y - dir.y * distance end for 
Enter fullscreen mode Exit fullscreen mode

Before we finish the loop let's draw a line that connects all points. We do that using the ‘gfx.line’ function, it takes from 4 to 6 arguments.

for i in range(1, points.len - 1) dir.x = points[i - 1].x - points[i].x dir.y = points[i - 1].y - points[i].y dir.normalize points[i].x = points[i - 1].x - dir.x * distance points[i].y = points[i - 1].y - dir.y * distance // Draw line gfx.line( points[i].x, // start x points[i].y, // start y points[i-1].x, // end x points[i-1].y, // end y color.gray, // color 16) // line width end for 
Enter fullscreen mode Exit fullscreen mode

The complete script should be something like this:

clear distance = 64 points = [] Point = new Sprite Point.image = file.loadImage("bone.png") Point.scale = 6 Point.normalize = function length = sqrt(self.x * self.x + self.y * self.y) if length != 0 then self.x /= length self.y /= length else self.x = 0 self.y = 0 end if end function for i in range(2) p = new Point display(4).sprites.push p points.push p end for anchor = new Point anchor.x = 480 anchor.y = 320 dir = new Point while true gfx.clear color.white // forward iteration points[points.len - 1].x = mouse.x points[points.len - 1].y = mouse.y for i in range(points.len-2, 0) dir.x = points[i + 1].x - points[i].x dir.y = points[i + 1].y - points[i].y dir.normalize points[i].x = points[i + 1].x - dir.x * distance points[i].y = points[i + 1].y - dir.y * distance end for // backwards iteration points[0].x = anchor.x points[0].y = anchor.y for i in range(1, points.len - 1) dir.x = points[i - 1].x - points[i].x dir.y = points[i - 1].y - points[i].y dir.normalize points[i].x = points[i - 1].x - dir.x * distance points[i].y = points[i - 1].y - dir.y * distance //Draw line gfx.line( points[i].x, points[i].y, points[i-1].x, points[i-1].y, color.gray, 16) end for yield end while 
Enter fullscreen mode Exit fullscreen mode

Done! Hit F5 and move the mouse around, you'll see the robot arm trying to reach it and flex if it's in range.

This is a simple example but it can be adapted to more complex ones with some additions. Want to see how? Wait for the next tutorial to find out.

That's it for now, hope you enjoyed it and find it useful! If you have any questions feel free to leave a comment. Thanks for reading!!

Top comments (1)

Collapse
 
joestrout profile image
JoeStrout

That is really cool. Inverse kinematics sounds like such deep magic, but the way you present it, it seems simple!