Forward Kinematics (FK) is a positioning algorithm. It's not as complicated as it sounds if you associate it to sequence of actions from an excavator operator's point of view. Beginning with the node closest to it, the operator configures the orientation of each node in the hope that the scoop will reach the location intended. Often, several attempts are made before achieving success. FK is an algorithm to perform such task. In this tutorial, we shall attempt the implementation of FK and apply it onto an excavator from a 2D side view. Having understood this algorithm, one can easily adapt it to a 3D environment. Enjoy!
Step 1: Tutorial Flow
This tutorial will assume the following sequence:
- Explanation for FK's Mathematical background.
- Hard-coded mplementation will be done in a file according to sequential flow.
- Next, object-oriented class developed to implement FK algorithm.
- Demonstration of FK class used in a project.
- Graphics applied to the demonstration.
Step 2: Chain
First of all, we must understand the concept of a chain. A chain abstracts the hierarchical order of nodes. There is a root node, the highest order of the chain, where all other nodes are joined to. Node that connects to the root is the first level child. Node that connects to first level child is the second level child.
The hierarchical relationship is simplified below:
root > node1 > node2 > node3 > node4 > node5 ...
The flash animation above shows the parent (higher in hierarchy) to child (lower in hierarchy) relationships, where each arrow points to a child node.
Step 3: Arrows
Each of these nodes are connected to adjacent ones by a constraint. I shall refer these constraints as "Arrows". Arrow are vectors. They have a length (better known as magnitude), and an angle that describes its orientation. In this tutorial, these arrows point from parent node to child node. For example, node1 is joined to root by arrow1. Arrow1 is pointing from root to node1.
Arrows follow a hierarchical order as well because they attempt to join a hierarchical chain of nodes. This heirarchical relationship is simplified as below:
arrow1 > arrow2 > arrow3 > arrow4 > arrow5 ...
So where's arrow0? Well, root is the highest in hierarchy. No arrow points to it so there's effectively no arrow0.

Step 4: Delta Rotation
Adjusting the rotation of arrows is the most important thing in the FK algorithm. Here, we shall rotate an arrow by delta angle. If we rotate arrow1, node1's position will update. But that's not all.
In FK algorithm, kinematics traverse from highest to lowest in hierarchy. Let us assume our chain has 6 nodes, joined by 5 arrows. If delta rotation is applied to arrow1, then arrow2 to arrow5 need to be rotated by delta angle as well. I have bolded the arrows that are affected by the change.
arrow1 > arrow2 > arrow3 > arrow4 > arrow5
If delta rotation is applied onto arrow3, then only arrow4 and arrow5 are affected.
arrow1 > arrow2 > arrow3 > arrow4 > arrow5
For each affected arrow, its associated node's position will also be affected. So node4 and node5 will update their positions.
In the above SWF, click the arrows to see the kinematics ripple down the nodes (darker arrows).
Step 5: Start Project
Lauch FlashDevelop and start a new Project. Add a "MileStone1.as" into your project.




Step 6: A Chain of Two Nodes: Two Graphics
Now to implement our first FK chain. First, we need to add the two sprites onto the stage as class variables. The root node is a blue circle and the first node is a purple circle.
rootNode = new Sprite(); rootNode.graphics.beginFill(0x4499FF); rootNode.graphics.drawCircle(0, 0, 20); rootNode.graphics.endFill(); addChild(rootNode); node1 = new Sprite(); node1.graphics.beginFill(0x772255); node1.graphics.drawCircle(0, 0, 20); node1.graphics.endFill(); addChild(node1);
Step 7: A Chain of Two Nodes: Arrow Constraints
node1
is joined to rootNode via vec1
, which is an instance of Vector2D class (custom-written class). It is a class variable. We initiate it and set its magnitude to 60.
vec1 = new Vector2D(0, 0); vec1.setMagnitude(60);
Step 8: A Chain of Two Nodes: Positioning
Before we start assigning listeners to add interactivity to our little program, we need to set the initial position of the nodes. First the rootNode
, which is pretty obvious. Next is node1
(highlighted) which depends upon rootNode
's position.
//setting the nodes rootNode.x = 150; rootNode.y = 150; node1.x = rootNode.x + vec1.x; node1.y = rootNode.y + vec1.y;
Step 9: A Chain of Two Nodes: Listener
Almost there. We add a listener to keyboard event. On pressing an arbitary key, vec1
rotates and further change the orientation of node1
with respect to rootNode
.
stage.addEventListener(KeyboardEvent.KEY_DOWN, move); } private function move(e:KeyboardEvent):void { vec1 = vec1.rotate(Math2.radianOf(10)); node1.x = rootNode.x + vec1.x; node1.y = rootNode.y + vec1.y; }
Below is MileStone1 completed. Simple, right?
Click the SWF, then hit any key (repeatedly) to see the effect.
Step 10: A Chain of Three Nodes: Graphics & Arrow
Now that we have got the basic setup, lets push this a little further. We will add node2
(also purple) to the existing chain. node2
is joined to node1
via vec2
, which is given a magnitude of 60
//a second child node2 = new Sprite(); node2.graphics.beginFill(0x772255); node2.graphics.drawCircle(0, 0, 20); node2.graphics.endFill(); addChild(node2); vec2 = new Vector2D(0, 0); vec2.setMagnitude(60);
Step 11: A Chain of Three Nodes: Positioning
We initialise the position of node2
in relation to node1
using vec2
as a constraint using the following code. Remember to also include the same piece of code into the keyboard listener (line 64 of MileStone2).
node2.x = node1.x + vec2.x; node2.y = node1.y + vec2.y;
Here's the MileStone2 completed:
Again, use the keyboard to see the effect.
Step 12: A Chain of Three Nodes: Uh-oh
Hmm... Something is wrong with this FK implementation. See, the kinematic does not traverse down the chain. Recall Step 4: Delta Rotation; kinematics need to ripple down from the current level of hierarchy to the end. I've highlighted the lines of code that do this. To fix the logical error, just paste line 65 to MileStone2. I've supplied MileStone3 just in case you face difficulties.
private function move(e:KeyboardEvent):void { vec1 = vec1.rotate(Math2.radianOf(10)); node1.x = rootNode.x + vec1.x; node1.y = rootNode.y + vec1.y; vec2 = vec2.rotate(Math2.radianOf(10)); node2.x = node1.x + vec2.x; node2.y = node1.y + vec2.y; }
Step 13: A Chain of Three Nodes: More Controls
Next, lets add flexibility to our control. Currently we control the positioning of node1
and node2.
Lets allow control on positioning of node2
only.
private function move(e:KeyboardEvent):void { if (e.keyCode == Keyboard.PAGE_UP) { vec1 = vec1.rotate(Math2.radianOf(10)); node1.x = rootNode.x + vec1.x; node1.y = rootNode.y + vec1.y; vec2 = vec2.rotate(Math2.radianOf(10)); node2.x = node1.x + vec2.x; node2.y = node1.y + vec2.y; } else if (e.keyCode == Keyboard.PAGE_DOWN) { vec2 = vec2.rotate(Math2.radianOf(10)); node2.x = node1.x + vec2.x; node2.y = node1.y + vec2.y; } }
Use the Page Down and Page Up keys to move the different nodes.
Yes, rotation is still one way-- counter clockwise. The purpose of running through Steps 5 - 13 is to build a solid understanding of the FK algorithm. Now that it's built, let us bring this to another level: designing an FKChain class that allows an easy implementation of FK.
Step 14: FKChain Class: Variables
FKChain was designed to easily implement FK. I have designed the class with the following variables and functions. I will run you through them.
Variables | Purpose | Data Type |
nodes |
To hold nodes of FK chain |
Vector array of Sprites
|
arrows |
To hold arrows connecting nodes |
Vector array of Vector2D
|
lowerLimits |
To hold lower boundary of allowable angle |
Vector array of Number
|
upperLimits |
To hold upper boundary of allowable angle |
Vector array of Number
|
selectedIndex |
To hold data of current selected node | Integer |
Step 15: FKChain Class: Data Structure
Association of nodes to arrows along with other constraints is depicted by the following diagram.

Note the association of nodes
to arrows
, lowerLimits
and upperLimits
. Angle marked in red is constrained according to the formula in image above.
Note as well that I mentioned in Step 3 that there is no arrow[0]
. However if we were to implement boundaries, we will need arrow[0]
to be a horizontal vector where angle of arrow[1]
is measured from and evaluated by the constraints.
Step 16: FKChain Class: Functions
Now that you understand the bolts and nuts of FKChain class, we move on to define functions of FKChain.
Functions | Input/ Data Type | Purpose |
FKChain() |
root node/ Sprite
|
Initiates class variables. Sets the root node into FK chain along with relevant constraints. Sets selectedNode to root node. |
appendNode() |
next child node/ Sprite
|
Append new node into chain along with relevant constraints. Sets current selected. Sets selectedNode to new node. |
removeNode() |
node to remove/ Sprite
|
Remove input node from chain along with relevant constraints. |
selectedNode() |
node to manipulate/ Sprite
|
Set current selected node to perform manipulations like adjusting its arrow (magnitude and angle) and limitations on the arrow angle. |
updateLimits() |
lower, upper bounds/ Number
|
Sets new limitations (upper and lower boundary) on the arrow's angle of selected node. |
alterMagnitude() |
magnitude/ Number
|
Sets new magnitude on the selected node's arrow. |
alterAngle() |
angle to adjust to/ Number
|
Inputs delta angle to selected node's arrow. |
setPosition() |
(none) | Ripple down kinematics from selected node's hierarchy to the end of the chain. |
Step 17: FKChain Class: Properties
I also introduced a few getters to facilitate development. They are used when scrolling through nodes.
Property | Purpose |
currentNode |
Returns current selected node. |
child |
Returns the child of selected node. |
parent |
Returns the parent of selected node. |
Step 18: Using FKChain
Now we've got an overview of FKChain, let's have a look at its application and internal workings. In your project, create a class TestChain
. We shall call upon a draw method to draw all required Sprites
on our stage. There are altogether five sprites and a bracket to indicate current selected node.
public function draw():void { b0 = new Sprite(); b0.graphics.beginFill(0); b0.graphics.drawCircle(0, 0, 15); b0.graphics.endFill(); addChild(b0); b1 = new Sprite(); b1.graphics.beginFill(0); b1.graphics.drawRect(-80, -10, 80, 20); b1.graphics.endFill(); addChild(b1); b2 = new Sprite(); b2.graphics.beginFill(0); b2.graphics.drawRect(-40, -10, 40, 20); b2.graphics.endFill(); addChild(b2); b3 = new Sprite(); b3.graphics.beginFill(0); b3.graphics.drawRect(-40, -10, 40, 20); b3.graphics.endFill(); addChild(b3); b4 = new Sprite(); b4.graphics.beginFill(0); b4.graphics.drawRect(-40, -10, 40, 20); b4.graphics.endFill(); addChild(b4); target = new Sprite(); target.graphics.lineStyle(3, 0xFF0000); target.graphics.moveTo( -15, -10); target.graphics.lineTo( -15, -15); target.graphics.lineTo( -10, -15); target.graphics.moveTo( 15, -10); target.graphics.lineTo( 15, -15); target.graphics.lineTo( 10, -15); target.graphics.moveTo( 15, 10); target.graphics.lineTo( 15, 15); target.graphics.lineTo( 10, 15); target.graphics.moveTo( -15, 10); target.graphics.lineTo( -15, 15); target.graphics.lineTo( -10, 15); addChild(target); }
Step 19: Initiate FKChain
public function initChain():void { c = new FKChain(b0); c.appendNode(b1); c.alterMagnitude(120); c.updateLimits(Math2.radianOf( -90), Math2.radianOf(45)); c.appendNode(b2); c.updateLimits(Math2.radianOf( -45), Math2.radianOf(90)); c.appendNode(b3); c.updateLimits(Math2.radianOf(0), Math2.radianOf(90)); c.appendNode(b4); //update all node's position b0.x = 250; b0.y = 300; c.selectNode(b1); c.setPosition(); //Place target onto root node initially target.x = c.currentNode.x; target.y = c.currentNode.y; }
First, we must instantiate the FKChain class by calling its constructor. Supply a root node into constructor. A further examination into the constructor function shows two main operations of the function. First is the instantiation of nodes
, arrows
, upperLimits
and lowerLimits
. Data is supplied into these variables thereafter.
Note that root node has an associated arrow. I did not explicitly show that arrows
are always one member less than nodes
. However, accept it for now as I explain on its use in later sections. The other highlighted lines are just fillers to ensure an easy addressing on constraints. Finally setting the currently-selected node to root
for further manipulation.
public function FKChain(node0:Sprite) { //instantiate variables nodes = new Vector.<Sprite>; arrows = new Vector.<Vector2D>; upperLimits = new Vector.<Number>; lowerLimits = new Vector.<Number>; //introduce root node nodes.push(node0); arrows.push(new Vector2D(1, 0)); lowerLimits.push(0); upperLimits.push(0); selectNode(node0); }
Step 20: Appending Nodes to FKChain
After initiation, we can start appending nodes to the instance of FKChain. The arrow's creation is abstracted from the users. I have highlighted the initial magnitude and limitation on angle of bend (to within -90 < angle < 90). These constraints are easily customised.
public function appendNode(node:Sprite):void { nodes.push(node); var newArrow:Vector2D = new Vector2D(0, 0); newArrow.setMagnitude(60); arrows.push(newArrow); lowerLimits.push(Math2.radianOf(-90)); upperLimits.push(Math2.radianOf(90)); selectNode(node); }
Step 21: Repositioning Nodes in FKChain
//update all node's position b0.x = 250; b0.y = 300; c.selectNode(b0); c.setPosition(); //Place target onto root node initially target.x = c.currentNode.x; target.y = c.currentNode.y;
Now the root node's position is at the top-left corner of stage. We shall move the location of b1
close to the center. Then we need to reposition the other nodes in chain and set targeting bracket to the currently selected node. Let us examine further setPosition
:
public function setPosition():void { var index:int = Math.max(1, selectedIndex); for (var i:int = index; i < nodes.length; i++) { //set position nodes[i].x = arrows[i].x + nodes[i - 1].x; nodes[i].y = arrows[i].y + nodes[i - 1].y; //set orientation nodes[i].rotation = Math2.degreeOf(arrows[i].getAngle()); } }
This function attempts to reposition and reorient all nodes starting from currently selected node. I've set a cap on index (highlighted) because logically, as we perform relative repositioning, root node is not affected by any parent. The node highest in hierarchy to perform relative repositioning is b1
. It doesn't matter whether you write c.selectNode(b0)
or c.selectNode(b1)
before c.setPosition()
although it will affect the initial position of trageting bracket..
Step 22: Animating FKChain
public function TestChain() { this.draw(); this.initChain(); stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown); }
Now its time to animate the chain. We shall assign a listener to keyboard event.
private function keyDown(e:KeyboardEvent):void { //scroll through node selection if (e.keyCode == Keyboard.SPACE) { if (c.currentNode == b4) { c.selectNode(b1); } else { c.selectNode(c.child); } } else if (e.keyCode == Keyboard.UP) { c.alterAngle(Math2.radianOf( -1 * step)); c.setPosition(); } else if (e.keyCode == Keyboard.DOWN) { c.alterAngle(Math2.radianOf(step)); c.setPosition(); } target.x = c.currentNode.x; target.y = c.currentNode.y; }
On pressing the space key, the targeting bracket will scroll through all nodes - except root node, b0
. Pressing the Up arrow key will give a negative delta angle, -1 * step
; while the Down arrow key will give a positive delta angle, step
.
Here's a sample of TestChain.
Use the space bar and the Up and Down arrow keys to control it. Note how the constraints affect how far each node can "bend".
Step 23: Angle Constraint in FKChain
We used alterAngle()
in the listener. Now lets dissect the internal workings of it.
public function alterAngle(ang:Number):void { var index:int = Math.max(1, selectedIndex); //calc deviation between previous arrow and current arrow var deviation:Number = arrows[index - 1].angleBetween(arrows[index]); var future:Number = deviation + ang; //ensure ang within defined limits if (future > upperLimits[index]) { ang = upperLimits[index] - deviation; } else if (future < lowerLimits[index]) { ang = lowerLimits[index] - deviation; } //update relevant arrows for (var i:int = selectedIndex; i < arrows.length; i++) { arrows[i] = arrows[i].rotate(ang); } }
Deviation of current node's arrow from that of its parent is calculated, this is its current angle. Input angle is added to current angle to form future angle. All is well and this value should be output. However, we want to implement a minimum and maximum boundary so future angle will be checked against these values and adjusted accordingly. After that we output the value to update all relevant arrows.
Step 24: Enhancing Choppy Animation
You may notice the animation is choppy. You press the key, and it nudges a bit. We can enhance this a little by putting in acceleration and deceleration of angular velocity. But it will require some rewiring of event handlers. Note that the draw()
and initChain()
functions are carried forward from previous steps.
public function TestChain2() { this.draw(); this.initChain(); stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDown); stage.addEventListener(KeyboardEvent.KEY_UP, keyUp); stage.addEventListener(Event.ENTER_FRAME, animate); }
Step 25: Keyboard Event Handlers
On pressing directional keys, flags will be toggled true
. On releasing these keys, their associated flags will be toggled false
. These flags will be captured and evaluated for further operations by an onEnterFrame
event handler.
private function keyDown(e:KeyboardEvent):void { //scroll through node selection if (e.keyCode == Keyboard.SPACE) { if (c.currentNode == b4) { c.selectNode(b0); } else { c.selectNode(c.child); } } else if (e.keyCode == Keyboard.UP) { upFlag = true; } else if (e.keyCode == Keyboard.DOWN) { downFlag = true; } else if (e.keyCode == Keyboard.LEFT) { leftFlag = true; } else if (e.keyCode == Keyboard.RIGHT) { rightFlag = true; } } private function keyUp(e:KeyboardEvent):void { if (e.keyCode == Keyboard.UP) { upFlag = false; } else if (e.keyCode == Keyboard.DOWN) { downFlag = false; } else if (e.keyCode == Keyboard.LEFT) { leftFlag = false; } else if (e.keyCode == Keyboard.RIGHT) { rightFlag = false; } }
Step 26: Kinematics Variables
Below are the variables used for animating kinematics:
private var angVelo:Number = 0; //current angular velocity private var maxVelo:Number = 5; //setting a cap on angular velocity private var angAcc:Number = 0.2; //incremental step on angular velocity private var angDec:Number = 0.8; //decay multiplier on angular velocity private var step:Number = 5; //directional steps when moving the whole chain
Step 27: EnterFrame Event Handler
By capturing flags from keyboard event, we can further manipulate kinematics of the chain.
private function animate(e:Event):void { //moving the whole chain if (c.currentNode == b0) { if (upFlag) { b0.y -= step } else if (downFlag) { b0.y += step } if (leftFlag) { b0.x -= step } else if (rightFlag) { b0.x += step } } //adjusting angle else { if (upFlag) { angVelo -= angAcc; //setting cap on angular velocity angVelo = Math.max(-1*maxVelo, angVelo); } else if (downFlag) { angVelo += angAcc; //setting cap on angular velocity angVelo = Math.min(maxVelo, angVelo); } else { //decelerate when keys are released angVelo *= angDec } //set value to selected node's arrow. c.alterAngle(Math2.radianOf(angVelo)); } //refresh position c.setPosition(); target.x = c.currentNode.x; target.y = c.currentNode.y; }
Note that there's a slightly different handling of the root node. As it is being selected, we will move the whole chain up, down, left or right. While other children are being selected we pump into angular velocity, angVelo
, up or down within the minimum and maximum velocity. If keys are released (hence, all flags are turned false
), the current velocity decelerates. To further clarify this snippet above, do read my post on linear kinematics to get an idea of the above code.
An example of the finished work is shown below:
Again, use the space bar and Up and Down arrow keys; this time, note how smooth the motion is.
Step 28: Preparing Assets in Flash IDE
Now that the ground work for code base is done, we shall focus on cosmetic works on our project. I'm not a very good artist so do bear with my excavator drawing.
The graphics I drew are converted into MovieClips. One thing to bear in mind is that the registration point of graphics should be positioned towards the end of segment for the arms and scoop. I've highlighted the registration points with a red circle for all my assets in the screenshots below.




I have named these MovieClip above Root
, Arm1
, Arm2
, and Scoop
respectively
Another detail is that you need to press Ctrl + L to pop the library panel and export these assets for Actionscript. Right-click, select Linkage, and fill in the details as shown in the images below.


Step 29: Exporting Assets from Flash IDE
Once all the graphical assets are done, publish them in SWC format to be properly imported to FlashDevelop. Im using Flash CS3. Press Ctrl + Shift + F12 to bring up the Publish Settings. Select the Flash tab and turn on Export SWC. Then hit Publish.

Step 30: Importing Assets into FlashDevelop
Copy the new file (.swc) into the lib folder of your project.

Now that you have it inside your project, you still need to include into library. Right click on your .swc file and select "Add To Library".

Step 31: Check the Assets
Expand the list as shown in image below to check the names of variables. I've used the following names Arm1
, Arm2
, Root
and Scoop
. These are the class names associated with those graphics. We will use them later.

Step 32: Replacing Graphics with Assets
Finally we get to the last leg of this tutorial. We shall replace the existing draw()
method with this new one, drawAssets()
.
private function drawAssets():void { rootNode = new Root(); arm1 = new Arm1(); arm2 = new Arm2(); scoop = new Scoop(); addChild(arm1); addChild(rootNode); addChild(scoop); addChild(arm2); target = new Sprite(); target.graphics.lineStyle(3, 0xFF0000); target.graphics.moveTo( -15, -10); target.graphics.lineTo( -15, -15); target.graphics.lineTo( -10, -15); target.graphics.moveTo( 15, -10); target.graphics.lineTo( 15, -15); target.graphics.lineTo( 10, -15); target.graphics.moveTo( 15, 10); target.graphics.lineTo( 15, 15); target.graphics.lineTo( 10, 15); target.graphics.moveTo( -15, 10); target.graphics.lineTo( -15, 15); target.graphics.lineTo( -10, 15); addChild(target); }
Step 33: Adjust the Asset Constraints
Right now if you launch your application, you may see a cluster of drawings in the top left corner, or the whole excavator arm and body aligned in a funny manner. Adjust the magnitude constraints and the angular limitations on joins appropriately. I've included mine as below.
private function initChain():void { c = new FKChain(rootNode); c.appendNode(arm1); c.alterMagnitude(260); c.updateLimits(Math2.radianOf( -45), Math2.radianOf(45)); c.appendNode(arm2); c.alterMagnitude(100); c.updateLimits(Math2.radianOf( 0), Math2.radianOf(90)); c.appendNode(scoop); c.alterMagnitude(60); c.updateLimits(Math2.radianOf( 45), Math2.radianOf(135)); //update all node's position rootNode.x = 250; rootNode.y = 300; c.selectNode(rootNode); c.setPosition(); //Place target onto root node initially target.x = c.currentNode.x; target.y = c.currentNode.y; }
Step 34: Moving Horizontally
Further tweaks to keyboard control of the root node, I've eliminated the vertical movement of the whole chain so that the excavator (root) only moves in horizontal manner. The snippet below is placed into the Event.ENTER_FRAME
handler
if (c.currentNode == rootNode) { if (leftFlag) { rootNode.x -= step } else if (rightFlag) { rootNode.x += step } }
Step 35: Play with the Arm
Every single adjustment to the code base is done. Of course, you may customise it however you want. For now, scroll through the nodes using the Space key. For each node, play with the directional keys to see the excavator moving up and down.
Conclusion
That's all for this time around. I hope the class file I've put together helps to ease your forward kinematics somewhere in your simulation and games. Thanks for reading and drop some comments if you found a flaw in anything.
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post