Skip to navigation

Elite on the BBC Micro and NES

Orientation vectors

The three vectors that determine a ship's orientation in space

Each ship in the Elite universe has its own set of three orientation vectors, which determine its orientation in space. These are fundamental aspects of the Elite universe, and form the basis for the ship-moving routines in MVEIT (moving the universe), MVS4 (pitching and rolling our ship) and MVS5 (pitching and rolling other ships).

There are three different vectors - nosev, roofv and sidev - with each of them pointing along one of the ship's main axes, like this:

  • nosev points forward out of the nose of the ship
  • roofv points up out of the ship's sunroof... or it would if it had one
  • sidev points out of the ship's right view

So if we're looking at the Cobra Mk III on the title screen, like this:

The title screen in the BBC Micro version of Elite

then this is how the orientation vectors are arranged:

  • nosev points out of the nose, towards the bottom-right corner
  • roofv points out of the roof, coming out of the screen to the top-right
  • sidev points out of the right side, towards the bottom-left corner

(Note that there are five ships that have slightly different orientations to this standard model, namely Thargoids, Thargons, space stations, cargo canisters and asteroids. These orientations are described below.)

It might help to think of these vectors from the point of view of the ship's cockpit. From this perspective, the orientation vectors always look like this, with our ship at the origin:

  roofv
  ^
  |
  |
  |
  |    nosev
  |   /
  |  /
  | /
  |/
  +-----------------------> sidev

Every ship out there has its own set of orientation vectors, with nosev pointing out of that ship's nose, roofv pointing out of that ship's roof, and sidev out of that ship's right side. The orientation vectors are used in lots of places, for example:

  • When we draw the ship on screen, we take the shape as defined by its blueprint, and rotate it so that it aligns with its orientation vectors, so the ship we draw on screen points the right way
  • If an enemy ship is firing its lasers at us, we use that ship's nosev vector to work out whether it is pointing towards us, and therefore whether it can hit us
  • When enemy ships pitch or roll, we can apply these movements by rotating their orientation vectors

Our ship doesn't have its own set of orientation vectors - at least, not explicitly. This is because our own ship's orientation vectors always align with the main coordinate axes: sidev aligns with the x-axis, which always points to the right from the point of view of our cockpit, while roofv aligns with the y-axis and points up, and nosev aligns with the z-axis, which always points into the screen.

Storing the vectors in the ship data block
------------------------------------------

The three vectors are stored in bytes #9-26 of the ship's data block, so when we copy a ship's data into the internal workspace INWK, the vectors live in INWK+9 to INWK+26. Each vector coordinate is stored as a 16-bit sign-magnitude number, like this:

          [ INWK(10 9)  ]           [ INWK(16 15) ]           [ INWK(22 21) ]
  nosev = [ INWK(12 11) ]   roofv = [ INWK(18 17) ]   sidev = [ INWK(24 23) ]
          [ INWK(14 13) ]           [ INWK(20 19) ]           [ INWK(26 25) ]

We can refer to these three vectors in various ways, such as these variations for the nosev vector:

  nosev = (nosev_x, nosev_y, nosev_z)

        = [ nosev_x nosev_y nosev_z ]

          [ nosev_x ]
        = [ nosev_y ]
          [ nosev_z ]

          [ (nosev_x_hi nosev_x_lo) ]
        = [ (nosev_y_hi nosev_y_lo) ]
          [ (nosev_z_hi nosev_z_lo) ]

Orthonormal vectors
-------------------

The three orientation vectors are orthonormal, which means they are orthogonal (i.e. they are perpendicular to each other), and normal (i.e. each of the vectors has length 1).

We can rotate a ship about its centre by rotating these vectors, as in the MVS4 routine (see the deep dive on pitching and rolling for more about this). However, because we use the small angle approximation to rotate in space, and it is not completely accurate, the three vectors tend to get a bit stretched over time, so periodically we have to tidy the vectors with the TIDY routine to ensure they remain as orthonormal as possible (see the deep dive on tidying orthonormal vectors for details).

Initialisation
--------------

When a new ship is spawned, its vectors are initialised in the INWK workspace by the ZINF routine as follows:

  sidev = (1,  0,  0)
  roofv = (0,  1,  0)
  nosev = (0,  0, -1)

So new ships are spawned facing out of the screen, as their nosev vectors point in a negative direction along the z-axis, which is positive into the screen and negative out of the screen.

Internally, we store a vector value of 1 as 96, to support fractional values, and the orientation vectors are stored as 16-bit sign-magnitude numbers. 96 is &60, and &60 with bit 7 set is &E0, so we store the above vectors like this:

  sidev = (&6000, 0, 0)
  roofv = (0, &6000, 0)
  nosev = (0, 0, &E000)

so nosev_z_hi = &E0 = -96, sidev_x_hi = &60 = 96 and so on.

How big is a unit vector?
-------------------------

As mentioned above, when new ships are spawned in the NWSHP routine, they are almost all initialised with nosev pointing towards us, out of the screen, and with roofv pointing up and sidev pointing right (though see below for some exceptions to this rule).

Planets are spawned in the same way as ships, i.e. with nosev pointing towards us, out of the screen, and with roofv pointing up and sidev pointing right. The orientation vectors are used to draw the planet's meridians and craters; for example, the crater is drawn at the end of the roofv vector, specifically when it is pointing away from us. See the deep dives on drawing craters and drawing meridians and equators for more details.

This means the crater is on the very top of the planet when we arrive out of hyperspace (or launch), and because the planet is set to pitch clockwise around the right-pointing sidev, it means roofv rolls to point towards us, and the crater is not shown immediately. If you launch from the station around a crater system like Zaonce, then the crater does indeed not appear for half a rotation, as roofv takes a while to rotate until it's pointing away from us.

Planet spawning gives us a way to understand the scale of the orientation vectors. As already discussed, unit vectors in Elite have length 96 (&60), which is represented by a 16-bit number (96 0) to mean 96.0. So the unit vector along the x-axis is (96, 0, 0), for example. Vectors are normalised to this length in the TIS2 routine, which is used to divide vector coordinates by their length, and TIS2 is called from the NORM routine when normalising vectors (see the deep dive on tidying orthonormal vectors for more details).

When we launch from a space station, the planet is spawned with a radius of K(1 0) = (96 0) = 24576. It is spawned at a distance of (z_sign z_hi z_lo) = (1 0 0), so this is the distance between the centre of the planet and the station. This is set up in the PLANET routine.

When we actually leave the station, the planet gets projected onto the screen. The planet's dimensions get divided by z, so to get the radius of the projected planet, we calculate (96 0) / (1 0 0). This projection division gives us a planet with an on-screen pixel radius of 96 pixels, and the space view is 192 pixels high and 256 pixels wide, so this means that on launching, the planet exactly fits the screen vertically, as 96 * 2 = 192. This is indeed the case, if you look at the size of the planet on launching.

So, to come back to roofv and the other orientation vectors, planets are spawned with a radius of (96 0), which is 96 * 256, or a radius of 256 unit vectors.

There is a twist on this, though. The scale of the orientation vectors is effectively an interpretation: we choose to interpret (96 0) as meaning 96.0, but we don't have to. When calculating the space station safe zone, for example, we actually interpret the unit vector length as being (96 0), rather than 96.0, so we get a vector that spans the radius of the whole planet. The unit vector (nosev, in this case) is exactly the same, we just choose to treat it as a much larger vector than when we are thinking in ship scale... so you could also argue that planets in Elite have a radius of one unit vector. It all depends on the context.

Rotation matrices and axes
--------------------------

Sometimes we might refer to the orientation vectors as a matrix, with sidev as the first row, roofv as the second row, and nosev as the third row, like this:

  [ sidev_x sidev_y sidev_z ]
  [ roofv_x roofv_y roofv_z ]
  [ nosev_x nosev_y nosev_z ]

though generally we talk about the individual vectors, because that's easier to understand. See the deep dive on calculating vertex coordinates for an example of the above matrix in use.

For the mathematically inclined, the three orientation vectors can be thought of as axes that define the 3D coordinate space orientated around the other ship - they form the basis for this space. To put it yet another way, the matrix above is a rotation matrix that transforms the axes of our ship into the axes of the other ship.

Finally, the orientation vectors define a left-handed universe, with the thumb as roofv, index finger as nosev, and middle finger as sidev.

Non-standard orientations
-------------------------

Not all ships are spawned with the nosev pointing towards us. For example, the space station is an exception; when we launch from the station, it is spawned with nosev pointing away from us, into the screen. This is because nosev points out of the station slot, and when we launch from it, we want the station to be spawned behind us, and with the slot facing forwards, in the same direction that we are looking. You can see this logic in the TT110 launch routine, which places the new station behind us before calling NWSPS to flip nosev before spawning the station with NWSHP.

The following ships don't have a standard orientation (all other ships follow the logical nose-roof-side pattern).

  • Thargoid mothership:
    • nosev points out of one side of the mothership
    • roofv points out of the other side of the mothership
    • sidev points out of the roof of the mothership
  • Thargon:
    • nosev points out of the Thargon's nose
    • roofv points out of the side of the Thargon
    • sidev points out of the roof of the Thargon
  • Space station:
    • nosev points forward out of the docking slot
    • roofv points out of the side of the space station in a direction that is parallel to the horizontal line of the slot
    • sidev points out of the side of the space station in a direction that is perpendicular to the horizontal line of the slot
  • Cargo canister:
    • nosev points out of the side of the canister, avoiding the apexes of the pentagonal cross-section and at right-angles to roofv
    • roofv points out of the side of the canister, through one of the apexes of the pentagonal cross-section
    • sidev points out of one end of the canister

The asteroid also follows its own orientation, but I'm not even going to try to describe which features appear to be the nose, roof and side, as they all just look like bumps to me.

One interesting (and presumably intentional) effect of the Thargoid and Thargon orientations can be seen when they pitch and roll. A pitching Thargoid actually spins like a traditional flying saucer (i.e. like a spinning top) as its roofv vector points out of its side (though a rolling Thargoid tilts back and forth as expected). When fighting Thargoids, you often find yourself orientating your ship to get them vertically aligned in your sights, which is because you can then track their sideways pitching with your own vertical pitching movement. This is different to the other ships, which expose their soft underbellies to your lasers when they try to pitch out of your way.

That's Thargoids for you. Different... and deadly.