Saturday, 19th March 2011

# Nodes and Edges

In this first tutorial, we will create our first 3D wireframe. We will:

- Define Node and Edge objects
- Define a Wireframe object consisting of Nodes and Edges
- Define a Wireframe cube

In order to display and manipulate objects on the screen, we have to describe them mathematically. One way to represent a simple shape is with a list of nodes, edges and faces.

- Nodes are points with three coordinates, x, y and z.
- Edges (sometimes called vertices) are lines connecting two nodes.
- Faces are surfaces bounded by multiple edges.

To start with we'll work with wireframe objects, which are only made up of nodes and edges. That way we won't have to worry about shading and figuring out which parts of the object are in front of which others until later.

## Objects

So let's start by creating some objects (in a file called wireframe.py) with the appropriate properties:

class Node: def __init__(self, coordinates): self.x = coordinates[0] self.y = coordinates[1] self.z = coordinates[2] class Edge: def __init__(self, start, stop): self.start = start self.stop = stop class Wireframe: def __init__(self): self.nodes = [] self.edges = []

We should also give the Wireframe class a couple of methods to make it easy to define the nodes and edges of a Wireframe object:

def addNodes(self, nodeList): for node in nodeList: self.nodes.append(Node(node)) def addEdges(self, edgeList): for (start, stop) in edgeList: self.edges.append(Edge(self.nodes[start], self.nodes[stop]))

Both these methods take lists, which I think is the most versatile approach. The addNodes function takes a list of coordinates. The addEdges function takes a list of pairs of node indices to join. For example, we can add three nodes and create an edge between the second two like so:

my_wireframe = Wireframe() my_wireframe.addNodes([(0,0,0), (1,2,3), (3,2,1)]) my_wireframe.addEdges([(1,2)])

Now would be a good time to add a couple of output functions to the Wireframe class to make debugging easier later on:

def outputNodes(self): print "\n --- Nodes --- " for i, node in enumerate(self.nodes): print " %d: (%.2f, %.2f, %.2f)" % (i, node.x, node.y, node.z) def outputEdges(self): print "\n --- Edges --- " for i, edge in enumerate(self.edges): print " %d: (%.2f, %.2f, %.2f)" % (i, edge.start.x, edge.start.y, edge.start.z), print "to (%.2f, %.2f, %.2f)" % (edge.stop.x, edge.stop.y, edge.stop.z)

## The Cube

I think the simplest shape to start working with is a cube. Although a tetrahedron has fewer sides, its sides aren't orthogonal, which makes things a bit trickier. When I tried to work out the coordinates of a regular tetrahedron, it turned out to be more complicated than I anticipated.

The nodes of a unit cube can be easily defined with a list comprehension:

if __name__ == "__main__": cube_nodes = [(x,y,z) for x in (0,1) for y in (0,1) for z in (0,1)]

If you're not familiar with list comprehensions, then now might be a good time to learn how they work, because I think they're one of the best things in Python and so use them whenever I can. The above list comprehension creates a list of every possible 3-tuple of the digits 0 and 1, thus defining the points of a unit cube.

(In this diagram, I represent the z-axis increasing as you move into the screen, but you could equally have it decreasing as you move into the screen. Similarly, the y-axis increases as you move up the screen, which standard for Cartesian axes, but not how computer graphics are generally displayed. I'll discuss this more in the next tutorial, but for now it's not really important.)

We can now create the nodes of our cube:

cube = Wireframe() cube.addNodes(cube_nodes)

The edges are a little trickier to define. I find it easiest to connect them in groups that are parallel. For example, if you look at the diagram below, you can see that the edges parallel to the x-axis connect the node pairs (0,4), (1,5), (2,6) and (3,7), so we can define them with a list comprehension:

cube.addEdges([(n,n+4) for n in range(0,4)])

The remaining edges can be added like so:

cube.addEdges([(n,n+1) for n in range(0,8,2)]) cube.addEdges([(n,n+2) for n in (0,1,4,5)])

Verify the cube has the properties you expect with:

cube.outputNodes() cube.outputEdges()

Now we have a cube object, but it only exists as an abstract object. In the next tutorial, we'll display it.

Attachment | Size |
---|---|

wireframe1.txt | 1.41 KB |

## Comments

Excellent tutorial! I hope you make more like this one, or go into more detail like lighting.

I just wanted to tell you this was the best intro tutorial on pygames 3D elements I've come across. I've been reading totorials for multiple toolkits on pyOpenGL for easily a month, and they all seem to skip the fundimentals and assume you're coming with a background programing in C or C++. Others simply said this stuff is found in the OpenGL Class discriptions, or handed you a completed mesh to bypass one of the most important fundimentals of OpenGL, vertex nodes and edges. Thank you for starting out with the real basics. This tutorial was clear, well documented and diagramed. I look forward to reading the rest of your tutorials.

Thanks, the tutorials start off simply as me trying to work out how 3D graphics can be achieved on a 2D screen, starting from first principles. I keep meaning to look at all the OpenGL stuff, but it looks very complex.

Absolutely amazing tutorial, I was looking for a good pygame 3D tutorial for so long now, and accidently came across it. It is definitely one of the best tutorial I have ever seen, and thank you so much for it!

Thanks for this tutorial! This one actually makes sense, and we get to create our own module to create 3D graphics! :D

Hi Peter,

You have noted "Pygame is not really designed for 3D graphics".

Can you advise what modules / libraries are really designed for 3D graphics?

## Post new comment