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
The full code for this tutorial is attached as a text file at the bottom of this page.

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.

Coordinates 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)])

Node indices of our cube

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.

AttachmentSize
wireframe1.txt1.41 KB