Skip to navigation

Aviator on the BBC Micro

Visibility checks

Deciding which lines to draw on-screen, and which to hide

Lines are at the heart of Aviator's 3D view (see the deep dives on lines and points and objects for more on this). Only lines from the fixed set get drawn in the 3D canopy view, and specifically, only lines that we can see out of the canopy - in other words, visible lines. Calculating the visibility of lines is therefore absolutely central to Aviator's main loop.

When the game starts, all 193 lines are marked as hidden; this means their IDs are put into the linesToHide list, while the linesToShow list is empty. As part of the setup process, the ShowOrHideLine routine is called for each line in turn, which checks the visibility of each line and moves any visible lines to the linesToShow list. Then the DrawCanopyView routine draws all the lines from the linesToShow list, and the initial view of the runway appears.

The ProcessLinesToHide routine now takes over responsibility for making sure lines appear when they should (and, along with them, the game's 3D objects). ProcessLinesToHide is called from part 12 of the main loop, but only when there are fewer than 35 points in the relatedPoints list (which takes priority as it contains points that we think might be in a currently visible object), and only if there is enough time (the routine is called in batches of three, and the code in the main loop makes sure we never spend more than 9 centiseconds of each main loop iteration processing the lines to hide list).

Each time the ProcessLinesToHide routine is called, it pulls a line ID from the end of the linesToHide list, and checks the line's visibility. If it is visible, then it moves the line to the linesToShow list so it gets shown on-screen, but if it is not visible, it sticks the line on the end of the linesToHide list and moves on to the next. In this way the game is constantly checking hidden lines to see if they are visible, so when we fly near enough to a line that it should be shown, this is the routine that shows it. (And, if that line is part of an object, then other lines in the object will also be shown, via the relatedPoints list.)

Main loop
---------

Let's take a look at the parts of the main loop that deal with visibility:

  • Part 2: Reset objectStatus for all objects and the relatedPoints list
  • Part 4: Call UpdateLinesToShow to go through all the lines in linesToShow, run ProcessLine on each one, and move any lines that aren't visible to linesToHide
  • Part 11: If relatedPoints contains 35 lines or fewer, call ProcessLinesToHide at least three times to processes lines from linesToHide and move them to linesToShow if they are visible, so currently hidden lines will eventually be shown when the plane gets close enough
  • Part 12: Call ProcessLinesToShow to process the whole linesToShow list, projecting points onto the screen for visible lines, or moving hidden lines to linesToHide
  • Part 12: Call DrawCanopyView to draw all the lines in the linesToShow list on-screen
  • Part 14: Reset the pointStatus list for the start and end points for any new lines that we added to linesToHide during the loop

You can read more about the main loop in the deep dive on program flow of the main game loop.

Processing lines
----------------

In the above, lines get processed at various points to work out their visibility and, if they're visible, we project their 3D coordinates onto the 2D screen. The ProcessLine routine processes an individual line by doing the following, first for the start point, and then for the end point:

  • Expand the point into its associated object point "strand" from the objectPoints table, pushing the point IDs onto the stack, and extracting the object ID (see the deep dive on 3D objects for more on strands and objectPoints)
  • Set the objectAnchorPoint to the last point ID from the extracted strand
  • Call SetObjectCoords for this object ID (see below), which does the following:
    • Check the distance to the object from the plane
    • If the object is close enough to be visible, call SetObjPointCoords to calculate the 3D coordinates for the object's anchor point
  • Call SetObjPointCoords for each point along the object strand, to calculate the 3D coordinates for each point in the strand. We do this by pulling all the point IDs from the stack, ending with the original start/end point from the line we passed to ProcessLine

Processing objects
------------------

The SetObjectCoords routine calculates the coordinates and visibility for an object, as follows:

  • Pre-process special objects:
    • Bullets (part 2): don't check the distance to the bullets, just check whether the back end of the bullet traces are above ground (if not, the bullets are below ground and not visible)
    • Object groups 6-9 (part 4): cycle through all eight objects in the group, and for each one set xObjectHi, zObjectHi to the values from xGroupHi, zObjectHi, so the object's anchor point gets set for the current object in the group
    • Dormant alien group 30 (part 6): if the Theme is enabled, cycle through all eight objects in the group, get the alienState and alienObjectId for the alien in the group, and use them to set the correct xObjectHi, zObjectHi values for that particular alien
    • Feeding and flying aliens 31, 32 and 33 (part 7): use the alienSlot, alienState, alienObjectId values to find the relevant object IDs for these aliens
  • Process the object (part 9):
    • Calculate the coordinates of the object's anchor point in the world's frame of reference, and store it in point ID = objectId + 216 (or in points 95-98 for the bullets)
    • Check the distance between the object and the plane in each axis, e.g. by doing xPoint = xObject - xPlane
    • If the object is too far away from the plane, move on to the next object in group (if this is an object group or dormant alien group), otherwise we are done
    • If the object is close enough to the plane to be visible, call SetPointCoords to rotate the object's anchor point by matrix 1 into the plane's frame of reference, and we are done

In this way we can calculate the visibility of individual points and their related strands and object points, and therefore we can calculate the visibility of each individual line. When it's all put together, this forms the core of Aviator's line-based visibility routine.