Itâs time to jazz things up a little. Time to implement whole-body rotation and translation!
In earlier posts, I explained how I derived inverse kinematics equations for a quadruped and implemented them in code. To clarify, inverse kinematics is a method of calculating joint angles to allow an end-effector to achieve a desired position; this is seperate from stable body positioning, which describes how the quadrupedâs body can be moved while keeping all its feet in the ground. In general, these movements can be categorized into two types: translation and rotation. Translation consists of moving forward-backward, side-side, and up-down while maintaining the bodyâs orientation with respect to the ground, while rotation describes pitch, roll, and yaw for the upper body. In this post, Iâm going to describe how I derived the formulas for these movements and combined them with my inverse kinematics engine to work consistently.
Letâs start with the rotation stuff, specifically, pitch. Hereâs what that looks like:
The first thing to point out is that rotations must keep the cneter of mass stable; they pivot around the center of mass, and donât just rotate. This means that increasing the height of the front legs while decreasing those of the back by the same amount wonât work, since this will cause the center of mass to move backwards. To counteract the motion backwards, it is necessary to lean forwards, which means pushing the feet forwards. But by how much? - Food for thoughtâŚ
Here are the diagrams Iâll refer to:
Diagram 1:

Diagram 2:

Where:
- a is the angle of pitch
- b is an arbitrary angle equal to a
- e is an arbitrary angle equal to a
and
- A is half the length of the robotâs upper body
- B is the distance from the shoulder directly to the ground
- C is the footâs distance on the z-axis relative to the robot
- D is the footâs distance on the x-axis relative to the robot
- E is an arbitrary distance below the foot along the x-axis relative to the robot
- F is an arbitrary distance below the foot along the z-axis relative to the robot
- G is the distance between the foot and the shoulder along an axis parallel to the ground
- L is the robotâs height from the ground.
To start, it is necessary to point out that all foot coordinates must be determined relative to the robot. Since the robot should be tilted, it is no longer parallel to the ground foot positions relative to the ground must be considered more seriously (Iâll get to this later), so it is necessary to transform the robot coordinates to the ground coordinates, or vice versa. My solution to this problem is to perform all calculations relative to the robot while considering only the foot position relative to the ground as the setpoint (which may sound obvious, but took me a while to figure out). In order to make this more clear, I decided to break up the footâs position into itâs relative x-y-z components â since this is pitch, only x-z componenets â which are represented by lengths C and D. These are the side lengths that can be fed to the inverse kinematics engine.
Firstly, angles a and b are equal (this can be proven easily by considering the 90° angle between sides A and C) and triangle B-C-D is right. It follows that:
\(C = B\cos(a)\)
\(D = B\sin(a)\)
As the pitch angle increases, however, the shoulder actually moves towards the axis of rotation and futher away from the foot (the distance between the shoulder and the foot is referred to as length G). This must be considered as well â refer to the up-close diagram.
\(G = A(1 - \cos(a))\)
\(F = G\sin(e)\)
\(E = G\cos(e)\)
Where F and E are offsets from C and D, respectively. The foot should lie at point
((C - F), (D + E)) relative to the robot.
Itâs as simple as that! This set of math should be applied to all four legs. Just donât forget to make sure that the offsets are applied to each leg correctly, and that the sides that are moving down and those moving up are considered. For example, the rear legs in the diagram are modeled a little differently:
And so on.
This can also be used to find roll, since that movement is inherently the same, but for a different set of axis. Imagining the diagram to describe the rear of the robot, it becomes obvious that pitch is identical to roll.
Hereâs the code I used for pitch:
for (int leg = 0; leg < LEG_COUNT; leg++) {
if (leg == 0 || leg == 1)
shoulderToGround = (float)(_originFootPosition.z + offsetZ) - (sin((float)pitchAngleL * ((float)PI / 180)) * ((float)BODY_LENGTH / 2));
else if (leg == 2 || leg == 3)
shoulderToGround = (float)(_originFootPosition.z + offsetZ) + (sin((float)pitchAngleL * ((float)PI / 180)) * ((float)BODY_LENGTH / 2));
outputFootPositions[leg].z += (cos(pitchAngleL * (PI / 180)) * shoulderToGround) - (_originFootPosition.z + offsetZ);
outputFootPositions[leg].x += (sin(pitchAngleL * (PI / 180)) * shoulderToGround);
twistZOffset = sin(pitchAngleL * (PI / 180)) * ((BODY_LENGTH / 2) - cos(pitchAngleL * (PI / 180)) * (BODY_LENGTH / 2));
twistXOffset = cos(pitchAngleL * (PI / 180)) * ((BODY_LENGTH / 2) - cos(pitchAngleL * (PI / 180)) * (BODY_LENGTH / 2));
if (shoulderToGround > (_originFootPosition.z + offsetZ)) {
outputFootPositions[leg].z += twistZOffset;
outputFootPositions[leg].x -= twistXOffset;
}
else if (shoulderToGround < (_originFootPosition.z + offsetZ)) {
outputFootPositions[leg].z -= twistZOffset;
outputFootPositions[leg].x += twistXOffset;
}
}The movement for pitch also makes sense intuitively, I think. In order to tilt downwards, the robotâs front height must decrease, and its rear legs must increase in height the same amount. Since this moves the center of the robot forwards, however, it is necessary to also shift backwards, which corresponds to a significant change in the x-axis coordinate of each foot. Finally, since tilting causes the shoulders of the robot to move further away from the feet, the feet must push outwards to keep the robot from falling flat.
Now itâs time to move onto yaw. For that, Iâm going to refer to this diagram:

Where:
- a is the angle of yaw
- b is an arbitrary angle equal to \(\tan^{-1}(\frac{C}{A})\)
- c is an arbitrary angle equal to b - a
and
- A is one-half the width of the robot
- B is the distance from the robotâs center to its shoulder
- C is one-half the length of the robot.
- D is an arbitrary distance from the center of the robot to its shoulder along the global y-axis (I have defined the x and y axis to be flipped)
- E is an arbitrary distance from the cneter of the robot to its shoulder along the global x-axis (I have defined the x and y axis to be flipped)\
- F is an offset relative to the robotâs shoulder along the global y-axis
- G is an offset relative to the robotâs shoulder along the global x-axis
First, let me point out that finding the offsets (lengths G and F) is the main goal of the calculation, since these are the lengths that can be fed to the inverse kinematics engine.
To understand how the calculations for yaw were derived, it is convenient to compare the robotâs un-yawed position (when angle a = 0) to itâs yawed position. Comparing the two positions makes it obvious that:
\(F = D - A\)
Thus, the difficulty is only in finding D and E. These can be found fairly straightforwardly when considering b, which is a constant:
And since b = c + a, it can be found that c = b - a. Calculating D and E is now a breeze:
\(E = B\sin(c)\)
\(D = B\cos(c)\)
Again, since
\(F = D - A\)
it is easy to find the distance the foot must be from the shoulder. Note that the height of the robot during the yaw doesnât need to be account for, since this is
considered by the inverse kinematics engine.
Hereâs the code for yaw:
for (int leg = 0; leg < LEG_COUNT; leg++) {
centerToCornerAngle = atan(((float)BODY_WIDTH / 2) / ((float)BODY_LENGTH / 2)) * (180 / PI);
centerToCornerLength = sqrt(pow((BODY_LENGTH / 2), 2) + pow((BODY_WIDTH / 2), 2));
footYOffset = centerToCornerLength * cos((90 - centerToCornerAngle - yawAngleL) * (PI / 180)) - (BODY_WIDTH / 2);
footXOffset = centerToCornerLength * sin((90 - centerToCornerAngle - yawAngleL) * (PI / 180)) - (BODY_LENGTH / 2);
if (leg == 0 || leg == 2) {
outputFootPositions[leg].y += footYOffset;
if (leg == 0) outputFootPositions[leg].x += footXOffset;
if (leg == 2) outputFootPositions[leg].x -= footXOffset;
}
if (leg == 1 || leg == 3) {
outputFootPositions[leg].y -= footYOffset;
if (leg == 1) outputFootPositions[leg].x -= footXOffset;
if (leg == 3) outputFootPositions[leg].x += footXOffset;
}
}So what about translation? Well⌠that was already solved by the pitch and roll calculations! The calculations used to account for the robotâs center moving further away from the shoulder can be used for translation as well â the translation amount is simply an input for G, and lengths F and E can be derived from there. If there isnât a tilt, then G = E; conveniently, since e is the angle of tilt and cos(0) = 1, there is no need to change this calculation at all; simply assume that there is always an angle of tilt using the calculation from before, and translation will be account for.
Along the z-axis, things become even easier; simply use the robotâs reference height as the sum of its nominal height and z-axis translational offset.
Finally, it is important to point out that all the offsets solved for above should be accumulated before being sent to the inverse kinematics engine in order to be considered during a combination of movements.
Unfortunately, due to a hardware failure on my PCB (relay exploded⌠âunderspeccedâ) I donât have a comulative recording of yaw or translations working. Once I make my new pcb (in the works now!) Iâll add a video showing this stuff in action. Itâs really cool!
Problems... so far:
- I havenât found any! I ran extensive testing before my quadrupedâs motherboard broke (again, relay failure) and it worked astoundingly well. In total the code is roughly 150 lines⌠definitely one of the larger functions in my code.
- In the future, I hope to solve this problem using linear algebra instead. Before solving this problem myself, I found multiple solutions (like this one, which applies it to posture correction) that used linear algebra to accomplish the same thing⌠and it is significantly less math. My senior year of high school, Iâm going to take a linear algebra course in order to learn how this works.
I hope this helps! This solution took me nearly a week to figure out - the answer is much easier than I wouldâve ever expected. Also, please note that Iâm not releasing this on github yet; Iâm in the middle of walking gait development, which Iâd like to finish before making a new release. For that reason, github wonât be updated.
Thatâs all for now!