Monday, 4th April 2011
Projecting 3D objects
In the previous tutorial we created a three-dimensional cube object, now we want to display it on a two-dimensional screen. In this tutorial, we will:
- Create a simple Pygame window
- Project an image of our 3D object onto the 2D window
As before, you can find the final code is at bottom of the page as a text file.
3D Projections
In order to display our cube we need to convert 3D coordinates, (x, y, z), into 2D coordinates (screen_x, screen_y). This mapping from a 3D coordinate system to a 2D coordiante system is called a projection. You can imagine that we're shining a light from behind our 3D object and looking at the shadow it casts on a 2D screen. In fact, since our retinas are essentially 2D, all we ever see are projections of objects (albeit stereoscopic projections).
There are many different way to project a 3D object onto a screen (see types of projection on Wikipedia), corresponding to viewing the object from different angles and perspectives. The simplest projection is to imagine that we're looking at our cube head on (so our "line-of-sight" is parallel to, or along, the z-axis). In this case, the z-axis contributes no information to what we see and we can simply ignore it. Since we're using a wireframe model, we don't need to pay attention to the order of elements along the z-axis.

In terms of vector transformations, we are using the linear transformation: T(x,y,z) → (x,y).
Basic Pygame program
Now, in a new file called wireframeDisplay.py or something similar, we can create a simple Pygame program to display a projection of our object. Hopefully you are familiar with at least the basics of Pygame. If not, you can look through the first couple of tutorials in my Pygame physics tutorial. The code below creates a deep blue window and displays it ready for our wireframe.
import wireframe
import pygame
width, height = 400, 300
background = (10,10,50)
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption('Wireframe Display')
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(background)
pygame.display.flip()
The projection viewer
Now let's create an class to deal with displaying projections of wireframe objects:
class ProjectionViewer:
def __init__(self, width, height):
self.models = {}
self.width = width
self.height = height
self.displayNodes = True
self.displayEdges = True
self.nodeColour = (255,255,255)
self.edgeColour = (200,200,200)
self.nodeRadius = 4
def addModel(self, name, wireframe):
self.models[name] = wireframe
The ProjectionViewer class has various attributes that control the display such as its dimensions, edge and node colours, and whether to display edges and/or nodes. It also contains a dictionary of wireframes and an addModel() method to add wireframes to this dictionary. Let's add a method to display its wireframes:
def display(self, screen):
for model in self.models.values():
if self.displayEdges:
for edge in model.edges:
pygame.draw.aaline(screen, self.edgeColour, (edge.start.x, edge.start.y), (edge.stop.x, edge.stop.y), 1)
if self.displayNodes:
for node in model.nodes:
pygame.draw.circle(screen, self.nodeColour, (int(node.x), int(node.y)), self.nodeRadius, 0)
The display() method takes a Pygame surface and draws nodes as circles at the (x, y) coordinates of the node, ignoring its z coordinate. It draws edges as anti-aliased lines between the relavant nodes' (x, y) coordinates.
We can now create wireframe cube as before (only a bit more compactly) and add it to a ProjectionViewer object.
cube = wireframe.Wireframe()
cube.addNodes([(x,y,z) for x in (0,1) for y in (0,1) for z in (0,1)])
cube.addEdges([(n,n+4) for n in range(0,4)]+[(n,n+1) for n in range(0,8,2)]+[(n,n+2) for n in (0,1,4,5)])
pv = ProjectionViewer(width, height)
pv.addModel('cube', cube)
To display the projection we call the following between filling the screen with the background colour and flipping the display.
pv.display(screen)
Fixing the coordinates
If you run the program now you will see a bit of a circle in the top left corner because we're drawing the circles for the nodes at (0,0), (1,0), (0,1) and (1,1). We can create a more sensible cube by changing the set of nodes we add to:
cube.addNodes([(x,y,z) for x in (50,250) for y in (50,250) for z in (50,250)])
Note that we don't actually have to change the z coordinates for moment, but we might as well. In the next tutorial we'll add methods for zooming and panning the display so we can view our unit cube (with 0 and 1 coordinates). Another point is that we are viewing our cube 'upside-down' since the y-axis actually starts at the top of the screen and goes down. We'll deal with this problem in later tutorial.
Now if you run the program you should see something like this:

This is what our cube looks like when we view it directly end on. It might seem like we've cheated. If you're familiar with Pygame then I'm sure you could have drawn a square and a few circles with a lot less effort. However, in the next two tutorials we'll deal with various transformations of the cube including rotations, which will hopefully convince you that we're actually looking at a 3D object.
| Attachment | Size |
|---|---|
| displayWireframe1.txt | 1.53 KB |
Post new comment