Learn the nuts and bolts of displaying wireframe 3D objects in Python and Pygame Zero, straight from the pages of this month’s Wireframe magazine, out now.
For many gamers, Elite (and more recently Elite Dangerous) needs little introduction. For those who are unaware, though, Elite was a pioneering space trading sim released in 1984 by Acornsoft for BBC B and Electron computers. Elite featured 3D wireframe graphics and enabled players to command a variety of spaceships and travel across thousands of star systems. If you want to play an emulated version of the BBC game, you can find an online version at bbcmicro.co.uk.
For this sample, we’re going to remake the title screen from the BBC B version, which features a spinning wireframe Cobra Mk III ship. We’re using Pygame Zero and some pretty nuts and bolts calculations to create the wireframe ship without a 3D library. Some of the maths may seem complicated, but it’s all fairly straightforward trigonometry. You can find the types of calculations I’ve used in several places on the internet, but I learned these techniques from a book called Advanced Programming Techniques for the BBC Micro by Jim McGregor and Alan Watt.
The first thing we’ll need is a set of coordinates which make up the lines of our wireframe model. We’re in luck, as the data for the Cobra Mk III can be found here. There are three coordinates for each vertex – x, y, and z – so that a point can be plotted in 3D space. These points are then joined up by faces, which is a list of the vertices that make up the face. To draw the 3D model to the screen, we need to convert these 3D coordinates into 2D screen coordinates.
The first part of this transformation is to set up the scene variables with a function called
initViewTransform(). We pass this function the parameters that make up the way the whole scene is displayed. These parameters are known as the radial distance (rho), the azimuthal angle (theta), the polar angle (phi), and finally, d, the distance from the viewer. With some use of cos and sin calculations, we can derive a set of multipliers for all our coordinates. We then transform each coordinate so that it’s rotated correctly in our scene using the
viewTransform() function. To get our wireframe onto the screen, we need to convert all our 3D coordinates into flat 2D coordinates using the
Now we can start thinking about drawing the model to the screen. All we need to do is run through our list of faces and draw a line between each transformed vertex listed as being part of that face. This will give us a wireframe model, but you’ll be able to see the faces at the back of the model as well as the front, and all the faces will look transparent. What we need is a way of only drawing the faces we can see – a technique called back-face culling. To do this, we calculate the ‘normal’ vector (a line pointing in the direction that the surface is facing) of each face and then compare that to the line of sight from our camera’s viewpoint. If the comparison comes back with a negative value, then the face is pointing away from the camera and we don’t draw it. We can also sort the faces to draw them from the back forward and draw the inside of the face in solid black to obscure any geometry that it covers. With these three ‘culling’ techniques, we end up with a solid-looking, wireframe model which we can rotate by changing the values of theta and phi. These calculations could be used to create any of the 3D geometry found in the original Elite.
Games are usually written in such engines as Unity, three.js, or Unreal these days, but it’s still a good idea to understand the fundamentals of how 3D scenes are created.
Get your copy of Wireframe #67
And if you prefer your magazines in digital form, you can also download Wireframe issue 67 as a free PDF!