Q:

How do I find the 2D direction to a 3D location?

I'm writing a 3D space flightsim, and I'm trying to display a 2D arrow on screen that points to the player's selected target. To clarify, the arrow needs to point in the direction that the player has to turn to reach the target.
I've tried several approaches but I can't seem to make this work for all cases. My current attempt involves constructing a plane from the player's local X/Y, projecting the target position onto it (to make the calculation 2D) then calculating the angle between the player's up and the vector to the projected target position.
I can't help but think I'm over-thinking this and there must be a better, simpler way. Anyone have any suggestions?
UPDATE:
Based on Sam's answer, I've tried the following code:
//Find the direction to the target, and normalize it
kglt::Vec3 target_dir = (target_pos - ship_pos);
target_dir.normalize();

kglt::Vec3 v1, v2;
v1 = ship_up.cross(ship_dir);
v2 = ship_up.cross(target_dir);
v1.normalize();
v2.normalize();

//Get the angle of rotation for the arrow
float angle = kmRadiansToDegrees(atan2(ship_up.dot(v1.cross(v2)), v1.dot(v2)));

But the returned angle is always around 180.0 (although normally -179.xx). Is there any obvious error somewhere? I'll continue debugging and update if I figure it out!
UPDATE2
Nevermind, the bug isn't in this section of code. My bad!

A:

I suggest the following method:

Build a vector pointing to the ship’s left, from its up vector and current direction.
Build a similar “left” vector using the target location, as if the ship was already pointing in the correct direction.
These two vectors lie in a 2D plane orthogonal to ship_up. You just want the angle between them.

Using GLSL syntax, assuming ship_up is already normalised (otherwise, add a normalize() call around it in the last line):
vec3 v1 = normalize(cross(ship_up, ship_dir));
vec3 v2 = normalize(cross(ship_up, target_pos - ship_pos));
float angle_radians = atan2(dot(ship_up, cross(v1, v2)), dot(v1, v2));

A:

Whilte trying Sam's idea, (which almost worked but I couldn't quite get the behaviour right), I found a bug in the functions that returned the ship's forward and up vectors. Once I'd fixed that, my original idea of projecting onto a plane worked fine. Here's the solution:
kglt::Vec3 projected;
kmPlane plane;

//Create a plane from the player's position, and forward vector
kmPlaneFromPointAndNormal(&plane, &ship_pos, &ship_dir);

//Project the target's position onto the plane
kmVec3ProjectOnToPlane(&projected, &target_pos, &plane);

//Calculate the direction to the projected point
kglt::Vec3 target_dir = (projected - ship_pos);

//Use atan2 magic, combined with the forward vector to calculate the correct angle for the arrow
float angle = kmRadiansToDegrees(atan2(-ship_dir.dot(ship_up.cross(target_dir)), ship_up.dot(target_dir)));

Thanks everyone!

