# 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: Thermonology

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... – Gustavo Maciel Jul 7 '13 at 1:46
• No, it definitely is not. For a multi-segmented armature, this approach is necessary. – Outurnate Jul 7 '13 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 Jul 7 '13 at 22:04
• I like the revised answer, thanks for explaining it clearly as well :) – Jkh2 Jul 10 '13 at 22:07

I would try to do it like this:

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


Now you just clear the turret and barrel rotations and apply these new. Not sure if it will work, but it's worth a try!

• Care to comment why -1? Even if the answer is not the right one, it's not misleading too. – Gustavo Maciel Jul 8 '13 at 1:14

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 – Quonux Jul 10 '13 at 20:05