4

I am making an air combat game, where you can fly a ship in a 3D space. There is an opponent that flies around as well. When the opponent is not on screen, I want to display an arrow pointing in the direction the user should turn, as such:

circle with red arrow at one corner like a radar map pointing the way to the enemy

The arrow will be on the HUD. It will not actually turn to the opponent. I want it to indicate which direction a user should turn to see the enemy ship. This arrow will circle around the cross-hairs (radius 60 pixels).

So, I took the camera location and the opponent location and did this:

double newDirection =
   atan2(activeCamera.location.y-ship_wrap.location.y,
   activeCamera.location.x-ship_wrap.location.x);

After which, I get the position on the circumferance of a circle which surrounds my crosshairs, like such:

trackingArrow.position = point((60*sin(angle)+240),60*cos(angle)+160);

It all works fine, except it's the wrong angle! I assume my calculation for the new direction is incorrect. Can anyone help?

John Calsbeek
  • 4,902
  • 2
  • 22
  • 17
Beaker
  • 143
  • 1
  • 6
  • 1
    This is a common question, typically in the form of "How can I make X face Y?" Seen here, here, here, here, here, and more but I'm running out of ch – House Apr 04 '12 at 15:39
  • If you have a 3D game, then what you're doing there with your snippet is not going to work since you need to know where your camera looks at. I assume you're parallel to the ground and want to turn your ship via a rotation (yaw in this case). You absolutely need the camDir vector. What you have to do then is __asin( cross ( camDir, normalize( shipPos - camPos) ) __ - in a nutshell. cross = cross product of 2 vectors - 2D or 3D. – teodron Apr 04 '12 at 15:50
  • @Byte56: We don't tell people that their question is a duplicate. We close the question as a duplicate. That only only tells people that it is a duplicate, it closes the question. – Nicol Bolas Apr 04 '12 at 21:06
  • @NicolBolas Well, it's not exactly a duplicate, but similar enough that they would be able to figure out what to do based on the other answers. – House Apr 04 '12 at 21:24
  • @Byte56: It's a duplicate. It may be in a different language, but it's all the same. – Nicol Bolas Apr 04 '12 at 21:36
  • How far off is the angle wrong? Is it simply a case that you have the coordinate system wrong?

    Also is your camera give you world space rather than screen space coords?

    – PhilCK Apr 04 '12 at 21:41
  • @Byte56 Not quite. I already have implemented "make X face Y" in reference to the enemy ship facing the player ship, basically my simple AI. – Beaker Apr 05 '12 at 01:35
  • @PhilCK I am using world space for the camera and enemy ship. The angle returned seems nonsensical, which is why I think it is incorrect. However, people here have given me some good advice that I will try to implement later today. – Beaker Apr 05 '12 at 01:37
  • Ah, you should edit your question then. – House Apr 05 '12 at 02:04
  • @Byte56 Fair enough. I tried to make it more clear. – Beaker Apr 05 '12 at 02:13
  • The atan2 method definitely should work. Two things that throw me off: One, if you're in 3D space I'm guessing Z is the up axis? Just making sure you didn't mean to write location.z and location.x. Two, shouldn't you subtract the camera's angle from the returned angle? Also, if you're coordinate system in the point() function has (0,0) as the top left pixel of the screen, you should negate the y value cosine term, so you'd have 160-60*cos(angle). This is because angle=0 should be at the top of the circle, eg the highest point on the circle, 160-60. –  Apr 05 '12 at 03:56

3 Answers3

4

I've seen the comment of Byte56, but the links there don't provide the exact answer, or aren't graphically detailed for this specific task, so if it's an answer the user wants, why not let him have one. Here goes. enter image description here

Imagine you're flying over the ground (the xy plane) and your ship's orientation is known (the green camDir vector). You want your camDir to coincide with the red vector (from your positions to your opponent's position,i.e. normalize(targetPos - camPos) = targetDir )

To turn in a relevant way for your picture description, you could rotate against the Z axis. To find the amount of rotation, you need to compute the cross product of camDir and targetDir. The cross product tells you the shortest turning orientation because |cross(a,b)| = |a| |b| sin(angle(a,b)) . Note that the angle between the two vectors is less than 180 degrees, in absolute value. Now, you can compute the dot product to find the cosine of the turning angle since dot(a,b) = |a| |b| cos(angle(a,b)).

The nice part is that both camDir and targetDir are unit/normalized vectors, so their cross and dot products provide you with the sine and cosine of the angle you want. Now just apply atan2 on the sine and cosine and retrieve the value of your angle (the dark-red arc in the image, for example). The blue circle around the plane is the same as your "radar" circle. That should be about it. So you need the camera direction, just the position of your ship is not enough - you have to "fire" in a direction, right?

teodron
  • 3,271
  • 20
  • 38
  • Excellent, thank you! It isn't positioning the ship I was worried about, it is finding the angle in a 3d and then mapping it to a 2d plane. The question isn't concerned with rotating to a position (perhaps I wasn't specific enough, most the the comments above were in reference to that), I can do that easy enough. It is simply an indicator of which direction a user should turn. I already have translation and rotation for both the user and enemy as well as firing. ;) – Beaker Apr 05 '12 at 01:37
  • 1
    You'd have to use the actual value of the cross product, because the length of a vector can't be negative (so if sin(theta) is negative the equation breaks down). Since A and B are on the same 2d plane, AxB has a Z component but an X and Y component of zero. So the Z component is sin(theta) WITH the sign. No length-taking necessary. –  Apr 05 '12 at 04:05
  • Sorry to burst your bubble, but this method is 100% equivalent in theory to his method IF you subtract the camera's angle from his result.http://mathbin.net/91914 –  Apr 05 '12 at 04:48
  • No bubble burst, they're equivalent. Both ways of reasoning make use of a very crude assumption: the plane performs only yaw (against the Z, perpendicular to the ground axis). Otherwise that radar scope doesn't make any sense. One thing I should say in my "defense" :P : I don't make use of an extra atan2 to compute the ship's angle, I assume the ship's direction is more useful to him, that's why I gave this solution. By the way, good latex explanation. – teodron Apr 05 '12 at 08:19
3

The big problem that I see is that you don't subtract the camera's angle from the calculated angle. You should have:

double newDirection = atan2(activeCamera.location.y-ship_wrap.location.y, activeCamera.location.x-ship_wrap.location.x)-cameraAngle;

If you don't then when the camera rotates, the direction will remain the same!

I also think you've got the coordinate system wrong somewhere. Here are some diagnostic questions:

  1. Is your Z axis the vertical axis? If it is you're fine. If not, replace .x and .y with the axes forming a flat plane (eg axes parallel to the ground).
  2. Does the coordinate system of the point(x,y) function have (0,0) as the top left pixel?

If it does then I see a problem with this line of code:

point(60*sin(angle)+240,60*cos(angle)+160);

The idea with that line is that when angle=0 you should have the topmost pixel, when angle=90 degrees it should be the leftmost pixel, and when angle=-90 it should be the rightmost pixel. Here, if angle=0 the coordinates are point(240,220) when - if (0,0) is the top left pixel - the value should be (240,100). (the topmost pixel).

Also, when angle=90 the pixel is point(300,160) when it should be (180,160). So you need to negate both:

point(-60*sin(angle)+240,-60*cos(angle)+160);

A similar line of reasoning could be used for other coordinate systems.

  • +1, you are right since I didn't include an upVector for the ship to take care of the fact that the sine resulting from the cross product is actually always positive: that angle is the least of the two possible rotation angles against the cross product vector, a rotation meant to align the first vector with the second. So, technically, I'd go with your answer. Perhaps specify that cameraAngle = atan2(camDir.y, camDir.x) for other users to understand easier this discussion. Cheers! – teodron Apr 05 '12 at 08:27
2

There's a pretty easy way to do it using the model and viewing matrices, assuming you're in first person.

Just run the opponent's position through the model and viewing matrices, and you'll get his position in view space. Disregard the Z component and normalize 2D vector made from the X and Y components, and presto, you've got a vector on the screen that points to your object. Note that this is already the coordinate of your arrow on-screen. If you also need the angle, it's atan2(y, x).

John Calsbeek
  • 4,902
  • 2
  • 22
  • 17