3
\$\begingroup\$

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!

\$\endgroup\$

4 Answers 4

5
\$\begingroup\$

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));
\$\endgroup\$
0
0
\$\begingroup\$

I'd simply transform the game/world coordinates of the target into screen coordinates. You might end up with a position outside the visible screen (like negative coordinates), but you'll still get directions for this. If the resulting coordinates are on screen, just check whether the target is in front of or behind the player and you're done.

\$\endgroup\$
0
\$\begingroup\$

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!

\$\endgroup\$
0
\$\begingroup\$

1) Project both player position P and target position T using the usual projection matrix.

2) Make

V = normalize(T - P),

which is the direction in the screen of the vector that connects player and target.

3) To display the arrow you could use a sprite aligned with the screen X axis = (1,0). You just have to rotate it accordingly with the slope of V. To get the angle, just apply the usual slope formula:

 tgTheta = (y2−y1) / (x2−x1)
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .