Skip to navigation

Calculating angles for drawing 3D objects

The trigonometry that converts 3D object definitions into on-screen polygons

When drawing 3D objects on-screen, the first challenge is to calculate where the object lives in the 3D world, and then we need to project that onto the screen. Only then can we move on to the drawing process itself, with its phases and back-face culling and colourful filled polygons.

The drawing process is described in the deep dive on drawing 3D objects, but for this article we're going to look at the maths that feeds into that process.

In order to understand the following, I highly recommend you read the deep dives on converting angles to coordinates and converting coordinates to angles, as they explain the fundamental geometry behind the following routines.

The GetObjectAngles routine
---------------------------

The first step in calculating object angles is to call the GetObjectAngles routine, to calculate the angles and distances of the vector from the viewer to the object we are drawing.

The result is relative to the viewer, which in this case is the player, so this gives us the position of the object's origin relative to the viewer. The results are returned via the following variables:

  • objTypeToAnalyse: Set to the type of the object we are analysing.
  • xDelta(Hi Lo), yDelta(Hi Lo), zDelta(Hi Lo): Calculate the difference (the delta) in all three axes between the viewer and the object we are analysing, to give us the 3D vector from the viewer to the object.
  • angle(Hi Lo): Calculate the angle of the hypotenuse of the triangle formed by the x-axis and z-axis, which is the projection of the 3D vector from the viewer to the object down onto the ground plane (so imagine a light shining down from above, casting the vector's shadow onto the y = 0 plane, and that's the hypotenuse).
  • hypotenuse(Hi Lo): Set to the length of the hypotenuse in the above triangle, so that's the length of the 3D vector from the viewer to the object when projected down onto the ground plane.
  • objectViewYaw(Hi Lo): The angle of the hypotenuse is the yaw angle of the 3D vector from the viewer to the object we are analysing. We subtract the viewer's yaw angle and add half a screen width to get the yaw angle delta from the viewer's gaze to the object, relative to the viewer's gaze, i.e. the screen. This gives us the yaw angle relative to the view. You can think of this as the screen x-coordinate of the object, or how far the object appears from the left edge of the screen.
  • objectGazeYaw(Hi Lo): Set to the object's gaze relative to the viewer's gaze. This is the difference in yaw angles between the viewer's gaze towards the object and the object's gaze to wherever it is looking (so that's the object's gaze relative to the viewer's gaze). If this is the landscape preview, rotate the object to face forwards and scale it so it looks good.
  • objectAdjacent(Hi Lo): Set objectAdjacent(Hi Lo) to hypotenuse(Hi Lo) so it can be used as the length of the adjacent side in the vertical right-angled triangle with the projected vector along the bottom and the vector from the viewer to the object as the hypotenuse. This means that hypotenuse(Hi Lo) and objectAdjacent(Hi Lo) are therefore same value, as the hypotenuse of the ground plane triangle is the same as the adjacent side of the vertical triangle. This therefore gives us the distance along the z-axis from the viewer to the object, which we can use to decide whether to draw the object's polygon edges in distinct colours.
  • objectOpposite(Hi Lo): Set to the length of the opposite side in the vertical right-angled triangle with the projected vector along the bottom and the vector from the viewer to the object as the hypotenuse. This therefore gives us the object's view-relative y-coordinate.

The routine also sets objectToAnalyse to the number of the object that we are drawing.

The GetObjPointAngles routine
-----------------------------

The second step in calculating object angles is to call the GetObjPointAngles routine, to calculate the view-relative pitch and yaw angles of all the points in the object and put them into the drawing tables at drawViewYaw(Hi Lo) and drawViewPitch(Hi Lo). We can use these as screen x- and y-coordinates, using the same equirectangular screen projection that is used in Revs.

It does this by working out the position of each point within the object, relative to the object's origin, so we can add this to the result from the previous section to give us the position of each point within the object, relative to the viewer (i.e. the position of the point on-screen).

More specifically, the routine loops through each point in an object definition and calculates the following:

  • Yaw angle: Calculate the yaw angle of the point, rotated by the rotation of the object itself (i.e. its gaze), so this is the yaw angle of the object point within the object, but with the correct rotation for the direction the object is facing.
  • xDelta(Hi Lo), zDelta(Hi Lo): Convert this yaw angle and the polar distance into x- and z-coordinates for the point in the y = 0 plane (i.e. on the ground) using cos(A) and sin(A) on a triangle with the line along the polar distance to the point as the hypotenuse. So these are the x- and z-coordinates of the object point within the object itself (i.e. relative to the object's origin and rotated to the correct gaze).
  • zDelta(Hi Lo): Add the z-coordinate to objectAdjacent(Hi Lo) to move the object point away from us to the correct distance, so we end up with the z-coordinate of the point relative to the viewer rather than the object's origin. We don't change the x-coordinate, so the next calculation is as if the object was directly in front of the viewer (instead, we add the x-coordinate in the drawViewYaw step below, after we do the angle calculation, when we add the object's view-relative yaw angle).
  • angle(Hi Lo): Calculate the angle of the right-angled triangle made up of the object point's x- and z-coordinates, to give us the yaw angle of the vector from the viewer to the point within the object, as if the object was directly in front of the viewer.
  • drawViewYaw(Hi Lo): Take this calculated yaw angle from the viewer to the object, and add it to the object's view-relative yaw angle to get the view-relative yaw angle of the point. This is the yaw angle of the vector from the viewer to the object point, with the object moved sideways into its correct x-axis position.
  • drawViewPitch(Hi Lo): Construct the vertical triangle that has the vector from the viewer to the point as the hypotenuse, the point altitude as the opposite side and the projection onto the ground of the vector as the adjacent side, and use this to calculate the pitch angle of the vector from the viewer to the object point.

Let's look at the last two calculations in more detail, where we calculate the pitch and yaw angles of the point from the perspective of the viewer, which gives us the screen coordinates we need to draw the polygon.

We do this by constructing two right-angled triangles, one flat on the ground (to calculate the yaw angle) and the other standing vertically (to calculate the pitch angle).

Let's start with the triangle on the ground.

The object has a view-relative yaw angle of objectViewYaw(Hi Lo), which was set up by the call to the GetObjectAngles routine in the previous section. This contains the yaw angle of the object relative to the view.

We just calculated the yaw angle of the object point relative to the viewer, for when the object is directly in front of the viewer, and put it in angle(Hi Lo). so if we add these two yaw angles together like this:

  angle(Hi Lo) + objectViewYaw(Hi Lo)

then this gives us the view-relative yaw angle of the object point, which is what we are trying to calculate in this routine, so we can store this result in drawViewYaw(Hi Lo).

We now move on to calculating the view-relative pitch angle of the object point by looking at the second triangle.

In the first triangle, we calculated the length and angle of the hypotenuse along the ground, which represents the projection from above of the vector from the viewer to the object point.

We're now interested in the y-axis element, so let's construct a triangle that stands vertically, and whose hypotenuse is the 3D vector from the viewer to the object point.

The hypotenuse that we used to calculate the yaw angle is now along the ground, acting as the bottom side of the new triangle, i.e. the adjacent side where the triangle's angle is the pitch angle.

The opposite side of the triangle is the height of the point above the ground (if we're looking up) or below ground (if we're looking down), which we can calculate using the objPointHeight table, which contains the height of the point within the object, and adding the view-relative y-coordinate of the object, which is in objectOpposite(Hi Lo).

Given these two sides, we can then calculate the pitch angle of our new triangle to give the view-relative pitch angle of the object point, which is what we are trying to calculate in this routine, so we can store this result in drawViewPitch(Hi Lo).

The results are then available to the DrawObject routine, which works out which polygons to draw and in which order, and actually draws the object on-screen. See the deep dive on drawing 3D objects for more information on this part of the process.