# How do I target a tank turret with an offset barrel?

I have a tank model consisting of multiple parts, a body, a turret and the barrel of the gun. The turret is offset from the body origin and can rotate around the Y axis(up). The barrel is connected to the turret and can rotate around the X axis of its connecting point to the turret.

I was wondering, given a target in 3d space and the position of the tank, how to calculate the correct rotations to give to the parts in order for the barrel to align itself to fire at the target.

The position of the tank cannot change, only the rotations given to the turret and the barrel. I would know how to solve this if the barrel is aligned with the z axis of the turret but I am having difficulty solving this problem if parts are offset from each other. Eventually I want to expand this so that multiple joined parts can get the end part (firing point) facing a target. I am using XNA C#.

EDIT:

Below is an example of what I mean, a turret connected to the body at the blue point. The barrel is connected to the turret at the red point:

Terminology

i call your "left/right rotation point of turret" joint "horizontal joint" and your "Up/down rotation point of barrel" joint "vertical joint".

Calculate with Forward kinematic (transformations leading to (previously unknown) orientation/position) the Position and Upvector and Forwardvector (this vector points from the horizontal joint forward).

Inverse Part After that use the Position and the Upvector and the Forwardvector to calculate the vertical and horizontal target rotations.

Formulas for the inverse Part of the case when the horizontal joint is directly on the vertical joint

(Pseudocode - untested)

// input
Vector3f UpVector; // upvector (normalized)
Vector3f ForwardVector; // (normalized)
Vector3f TargetPosition; // (absolute target position
Vector3f FKPosition; // forward kinematic Position of the turret
// is the absolute position in worldspace of the turret head

// temp
Vector3f SideVector; (normalized)

Vector3f TargetDiffUnnormalized; // difference to the target
Vector3f TargetDiff; // (normalized)

// output

// calculation

// first we calculate the sidevector which is perpendicular to the up and ForwardVector
SideVector = UpVector.Cross(ForwardVector).normalized();

TargetDiffUnnormalized = TargetPosition - FKPosition;
TargetDiff = TargetDiffUnnormalized.normalize();

// we "project" the TargetDiff vector to the Plane which is described by the ForwardVector and SideVector

float ProjectedForward = ForwardVector.dot(TargetDiff)
float ProjectedSide    = SideVector.dot(TargetDiff)

// now we need to normalize the 2d Vector which is described by ProjectedForward and ProjectedSide because the length of it is not 1.0 and we would get wrong results

Vector2f ProjectedHorizontal = new Vector2f(ProjectedForward, ProjectedSide);
ProjectedHorizontal = ProjectedHorizontal.normalized();

// NOTE< here the components _could_ be wrong and the multiplication _could_ be wrong, depending on the direction of rotation and so on >

if( ProjectedHorizontal.X < 0.0f )
{
}

//-----

Vector3f HorizontalDirection = ForwardVector.scale(ProjectedHorizontal.X) + SideVector.scale(ProjectedHorizontal.Y)

// NOTE< HorizontalDirection should be normalized, check if wanted with assertion to make sure >



Edit 1

The math and code and the description for the case if the horizontal joint is not on the vertical joint (the more general case) follows here.

If you look at the 2d case from above (so to say on the plane of the horizontal joint in the 3d world) you notice that when you rotate your horizontal joint the vertical joint spins on a circle.

So now we search the rotation offset, because you need to add the rotation to the HorizontalRotationRawRad variable to get the real rotation.

To archive this you need to project the target point in the 3d space to the horizontal joint plane (you can archive this with the calculation of the intersection between the plane and a ray which starts at the target point and points in the direction of the normal of the plane).

I call this point you get as the result of the intersection PlaneTarget.

Now you can calculate the distance from FKPosition to PlaneTarget and i call it c.

For the following calculation you need to calculate only once on gameloading or on the construction time of the tank or whenever only the distance from the horizontal joint to the vertical joint and the angle of the two (from 90 to 180 degrees) and the side of the vertical joint.

To calculate the offset angle we use the law of cosines:

We know c and b and gamma and we search alpha.

after some math...

c² = a² +b² - 2ab cos(gamma)
rearanging->
c² = a² + b² - a * 2b cos(gamma)

this is a quadratic equation, solve it with some math or a solver which you wrote to get c


we know a, notice to catch the cases if the target is inside the circle (c < b) because it wouldn't make much sense.

now we need to calculate the angle alpha with some more math...

a² = b² + c² - 2bc cos(alpha) | + 2bc cos(alpha) | - a²
2bc cos(alpha) = b² + c² - a²

alpha = acos(b² + c² - a² / 2bc)


now we can add/subtract alpha from/to VerticalRotationRawRad to get the correct angle for the horizontal joint.

The calculation of the vertical joint is a bit easier, use the angle of the horizontal joint to rotate the point of the vertical joint in worldspace.

Then just do a dot product between the normalized direction of the barrel and the difference to the target position (of course the result doesn't have a sign, if it should have one just use the horizontal plane and look if the target position is above or below it).

• I think this is overkill... Commented Jul 7, 2013 at 1:46
• No, it definitely is not. For a multi-segmented armature, this approach is necessary. Commented Jul 7, 2013 at 16:28
• This solution would definitely work for a barrel that is located directly at the rotating point of the turret but I still don't think it would work for an offset barrel. I have added an image to the original question to show what I mean. I am starting to think I need to apply some sort of IK convergence algorithm but it would be great if there existed a solution that does not solve this problem using iterative convergence methods. Please correct me if I am wrong about the above btw, I want to design the algorithm to work for any tank design.
– Jkh2
Commented Jul 7, 2013 at 22:04
• I like the revised answer, thanks for explaining it clearly as well :)
– Jkh2
Commented Jul 10, 2013 at 22:07
• I agree this is overkill. Quadratic equation is not necessary for this. And this is not a "multi-segmented armature". The offset turret is a single rotation, and can be solved separately from the vertical barrel angle. So its 2 separate single pivot calculations, one is just a bit more involved than the other. Commented Mar 7 at 4:43

Here I am over 10 years later providing another answer, hopefully this will help someone.

If we look at the tank image below, we know we can calculate the angle A° pretty easily with atan2. Rotating the turret by this amount will point the turret itself at the target, however the barrel will still be shooting to the left of the target due to the offset.

A° = atan2(to_target.y, to_target.x);


The trick is to imagine extending the length of the barrel such that the tip of the barrel would touch the target if we rotated the turret to the correct position.

If R is the distance from the pivot point to the target, and O is the barrel offset, then X is some distance which is less than R which is the exact length required for our "extended barrel" to be able to touch the target after we've rotated, and these form a right triangle.

We can also see that the correct angle we need to rotate by is C°, and this is relatively straightforward to calculate once we know B, which is also simple trigonometry. So the full solution is:

R = length(to_target);
B° = acos(O / R);
C° = A° + (90° - B°);


We could also calculate the (90° - B°) directly sin asin() which is less intuitive but a little cleaner. So here is the copy-paste solution:

float aim_the_turret(float offset, vec2 to_target)
{
// returns angle to aim turret to so that
// barrel is pointed at the target
return atan2(to_target.y, to_target.x)
+ asin(offset / length(to_target));
}


Note: when calculating the barrel offset O, we calculate just the lateral offset which is perpendicular to the forward direction of the turret. The intuitive explanation for this is that if we shift the position of the barrel forward or backward, it will still be aiming in the exact same direction. All that matters is the lateral offset from the pivot point.

The up/down rotation of the barrel works like this:

x is the distance to the target and y is the difference in altitude to the target

You need to rotate the gun up by alpha, which is atan2(y, x)

This is in side view. In your 3D-world where z is pointing up, x would be sqrt(xx + yy) and y would be z.

If you don't know what atan2 is, google it. It's the most helpful thing ever and C# has it too.

The left/right rotation of the turret works like this:

Your tank's gun is offset from the turret's origin by a vector v (red arrow).

So to hit your target, you need to rotate your turret by beta so that it (the turret, not the gun!) points at a spot that is offset from the target by minus v (green arrow). Makes sense? The rest is just basic trigonometry again (which I suck at, so use with caution).

Here's some pseudo-code that assumes that all the tank's component's positions are relative to their parents' (tank->turret->gun):

// first the turret's rotation:
vec3 absoluteTurretPosition = tank.position + turret.position;

vec3 pointToAimAt = target.position - gun.position;

vec3 delta = pointToAimAt - absoluteTurretPosition;

float desiredRotation = Math.Atan2(delta.x, delta.y);

turret.rotate(desiredRotation);

// now the gun's rotation:
vec3 absoluteGunPosition = absoluteTurretPosition + gun.position;

delta = target.position - absoluteGunPosition;

float x = Math.sqrt(delta.x * delta.x + delta.y * delta.y);

desiredRotation = Math.Atan2(x, delta.z);

gun.rotate(desiredRotation);

• -1 don't forget that you need ugly special case code for the rotation calculation if you use the (overused) Atan2 function it doesn't work Commented Jul 10, 2013 at 20:05
• This is a neat idea and might look good enough in a game, but it doesn't actually give the right answer. For example imagine the turret is currently oriented such that the red arrow in your diagram is pointing directly away from the target. If we apply the inverse of this to the target position, this will only move the target position directly away from the turret, and so it will have no impact on our final angle calculation. Commented Mar 7 at 4:50

I would try to do it like this:

Vector3 dir = target.pos - tank.pos;
dir.normalise();