Tuesday, 5th April 2011
Basic 3D transformations
In the previous tutorial we displayed a static cube wireframe, which appeared as a square. In order to get a sense of its three dimensions, we must be able to move it in three dimension. But first, we'll introduce some basic transformations, which don't require a third dimension. In this tutorial, we will:
- Add the ability to translate wireframes
- Add the ability to scale wireframes
- Apply these transformations using the keyboard
By the end of the tutorial, we'll be able to move and scale the square as shown in the video below. The final code is at the bottom of the page as text files.
Translation in 3D
The simplest transformation is a translation: moving the wireframe along one axis by adding a constant to the x, y or z coordinate of every node. For this, we add the following method to the ProjectionViewer class:
def translate(self, model, axis, d):
if axis in ['x', 'y', 'z']:
for node in self.models[model].nodes:
setattr(node, axis, getattr(node, axis) + d)
The translate() method takes the name of a wireframe model, the axis along which it should be translated, and the distance it will be translated. It then adds the distance, d, to the relevant coordinate of every node in the model. We use getattr() and setattr() so we can easily define which attribute we want to change.
For example, to move our cube 100 pixels to the right, we get and set the attribute 'x':
pv.translate('cube', 'x', 100)

Because the y-axis of the screen starts at the top and points down, to move our cube up 40 pixels, we call:
pv.translate('cube', 'y', -40)
Translating along the z-axis will have no noticeable effect at the moment.
Scaling in 3D
Scaling is also relatively straightforward. We could simply multiply the x, y and z values of each node by a scaling factor, which would have the effect of scaling the cube centred on the origin. However, I think it makes more sense to scale x and y, centred on the centre of the screen. We can achieve this by adding the following method to the ProjectionViewer class:
def scale(self, model, scale):
centre_x = self.width/2
centre_y = self.height/2
for node in self.models[model].nodes:
node.x = centre_x + scale*(node.x - centre_x)
node.y = centre_y + scale*(node.y - centre_y)
node.z *= scale
The function finds the centre of the screen then scales the distance of each node from the centre (ignoring the z coordinate). If we assume that the screen is at z=0, then nodes behind the screen move closer as we scale down, and further away as we scale up.
For example, to scale our cube by three quarters:
pv.scale('cube', 0.75)
Keyboard controls
We can associate the transformations that we've defined with keys and then call them (with an arbitrary value) in response to key presses. I've written in more detail on how this code works here. You can use whichever set of keys you find most logical.
key_to_function = {
pygame.K_EQUALS:(lambda x,ob: x.scale(ob, 1.25)),
pygame.K_MINUS: (lambda x,ob: x.scale(ob, 0.8)),
pygame.K_LEFT: (lambda x,ob: x.translate(ob,'x',-10)),
pygame.K_RIGHT:(lambda x,ob: x.translate(ob,'x', 10)),
pygame.K_DOWN: (lambda x,ob: x.translate(ob,'y', 10)),
pygame.K_UP: (lambda x,ob: x.translate(ob,'y',-10))}
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key in key_to_function:
key_to_function[event.key](pv, 'cube')
We can now manipulate our cube wireframe to a degree, but it still looks like a square. In the next tutorial, we'll introduce another transformation - rotation - which will finally allow us to see another side to our square and see that it really is a cube.
| Attachment | Size |
|---|---|
| displayWireframe2.txt | 2.75 KB |

Post new comment