Skip to navigation

Converting angles to coordinates

Using two-triangle maths to convert between angles and Cartesian coordinates

The GetVectorForAngles routine converts a vector from pitch and yaw angles into a 3D Cartesian vector. A similar process is used in the GetObjPointAngles routine to convert an object point's polar distance and angle into x- and z-coordinates.

Let's see how this process works in the GetVectorForAngles routine.

Essentially, the routine uses the rotation matrix routine from Revs to convert a vector from a pair of pitch and yaw angles into a Cartesian [x y z] vector. The pitch and yaw angles are 16-bit numbers as follows:

   vectorPitchAngle(Hi Lo)

   vectorYawAngle(Hi Lo)

The same vector, but expressed as a Cartesian vector, is calculated as follows:

  [ xVector(Lo Bot) ]
  [ yVector(Lo Bot) ]
  [ zVector(Lo Bot) ]

The calculation is this:

  yVector = sinVectorPitchAngle / 16

  zVector = cosVectorPitchAngle * cosVectorYawAngle / 16

  xVector = cosVectorPitchAngle * sinVectorYawAngle / 16

Let's run through the steps to get this result.

The GetVectorForAngles routine can convert any vector into angles, but to make things easier to follow, let's talk about converting a specific vector, namely the vector from the player's eyes to the sights, as that makes it easier to describe the various points involved. This conversion is done by the GetSightsVector routine, which in turn calls GetVectorForAngles.

We start by converting the pitch angle of the vector from the player's eyes to the sights into a Cartesian y-coordinate in the global 3D coordinate system, where the y-axis is the up-down axis. We store the resulting y-coordinate in the 16-bit variable yVector(Lo Bot).

We can calculate yVector by considering a triangle with the sights vector as the hypotenuse, and we drop the end point down onto the y = 0 plane (i.e. onto the ground).

Consider the case where the player is looking up at an angle of vectorPitchAngle, so the vector from the player's eye to the sights is from the bottom-left to the top-right in the following triangle:

                                  sights
                              _.-+             ^
                          _.-´   |             |
             vector   _.-´       |         y-axis (up)
                  _.-´           |
              _.-´               |  y
          _.-´                   |
       .-´ vectorPitchAngle      |
  eye +--------------------------+
                  p

To make the calculations easier, let's say the length of the vector is 1. Then trigonometry gives us the following:

  sin(vectorPitchAngle) = opposite / hypotenuse
                        = y / 1

So the y-coordinate is given by:

  y = sin(vectorPitchAngle)

which is the result we need to calculate for the y-axis element of the vector.

Incidentally, the adjacent side p, which is the length of the vector projected down onto the y = 0 plane, is calculated in a similar way:

  cos(vectorPitchAngle) = adjacent / hypotenuse
                        = p / 1

So p = cos(vectorPitchAngle), which we will use to calculate the x- and z-coordinates of the vector below.

Now that we have calculated the y-coordinate of the sights vector, we move on to converting the yaw angle of the sights vector into Cartesian x- and z-coordinates, where the x-axis is the left-right axis and the z-axis goes into the screen. We store the resulting x-coordinate in the 16-bit variable xVector(Lo Bot) and the z-coordinate in zVector(Lo Bot).

We calculate xVector and zVector by considering a triangle on the y = 0 plane, so that's a triangle on the ground.

The hypotenuse of this triangle is side p from the first calculation, which is the sights vector projected down onto the ground - the shadow from a light source directly above, if you like. The opposite and adjacent sides of this tringle will give us the x- and z-coordinates of the vector.

To see this, consider the same sights vector as before, with p as the shadow of the vector projected down onto the ground, and where the player is looking sideways at a yaw angle of vectorYawAngle.

This gives us a triangle like this, when viewed from above, so it's as if we are the light source directly above the sights vector, projecting the vector down onto p. The projected vector goes from the top-left to the bottom-right, with the sights end of the vector being higher than the eye end, as before:

                  z
  eye +--------------------------+       z-axis -->
       `-._ vectorYawAngle       |       into screen
           `-._                  |
               `-._              | x
                p  `-._          |
                       `-._      |        x-axis left
                           `-._  |          to right
                               `-+             |
                                   sights      v

Again, simple trigonometry gives us the following:

  sin(vectorYawAngle) = opposite / hypotenuse
                      = x / p

so:

  x = p * sin(vectorYawAngle)

Similarly, the triangle gives us:

  cos(vectorYawAngle) = adjacent / hypotenuse
                      = z / p

so:

  z = p * cos(vectorYawAngle)

We calculated above that:

  p = cos(vectorPitchAngle)

Substituting that into our result gives us:

  x = p * sin(vectorYawAngle)
    = cos(vectorPitchAngle) * sin(vectorYawAngle)

  z = p * cos(vectorYawAngle)
    = cos(vectorPitchAngle) * cos(vectorYawAngle)

So this gives us the x-axis and z-axis results for our vector and the conversion is complete.