So now, we’re about to render faces for the cube we rendered, which didn’t have any face :”(

image

Quick recap

In last post, we rendered a cube in tkinter canvas, with some vector stuff we were also able to rotate it and give it some animations, the cube only had the edges and circles representing vertices.

Now to render the face, first we can define the faces of the cube, exactly the way we did for edges, using list indices of vertices:

#   5----------1
#  /|         /|
# 4----------0 |
# | |        | |
# | 7--------|-3
# |/         |/
# 6----------2

vertices = [
    (100, 100, 100),
    (100, 100, -100),
    (100, -100, 100),
    (100, -100, -100),
    (-100, 100, 100),
    (-100, 100, -100),
    (-100, -100, 100),
    (-100, -100, -100)
]

faces = [
    (0, 2, 6, 4),
    (0, 1, 3, 2),
    (0, 4, 5, 1),
    (1, 5, 7, 3),
    (2, 3, 7, 6),
    (4, 6, 7, 5)
]

Now before rendering the edges and vertices, we can draw the faces with canvas widget’s create_polygon method:

def draw_mesh(vertices, edges, faces):
    for  face in faces:
        coords = [(vertices[i][0] + 250, vertices[i][1] + 250) for i in face]
        canvas.create_polygon(coords, fill="grey")

cube with faces

Now lets add some colors shall we!

cube with colored faces

Lighting

I’m not going to ray trace or something, but simply change the shade of the face based on the angle each face’s normal makes with that lightsource.

image

def draw_mesh(vertices, edges, faces):
    light_source = [500, -500, 500]
    light_vector = np.array(light_source)

    for face in faces:
        # crossing the diagonals will give us the normal vector to the face
        v1 = np.array(vertices[face[0]])
        v2 = np.array(vertices[face[1]])
        v3 = np.array(vertices[face[2]])
        normal = np.cross(v2 - v1, v3 - v1)

        # now find the angle bw
        to_light = np.array(light_source) - v1
        cos_theta = np.dot(normal, to_light) / (np.linalg.norm(normal) * np.linalg.norm(to_light))

        # use the theta value to generate a color
        shade = int(255 * (cos_theta + 1) / 2)
        color = '#{:02x}{:02x}{:02x}'.format(shade, shade, shade)

        coords = [(vertices[i][0] + 250, vertices[i][1] + 250) for i in face if isinstance(i, int)]
        canvas.create_polygon(coords, fill=face[4])

cube with shades

You do notice something is off right, the faces behind are sometimes drawn after the faces on the front are drawn, that makes the cube feel like we can see through. We definitely don’t want that, so for now, we will just find which face is closer and draw it very last.

def draw_mesh(vertices, edges, faces):
    ...
    # bit of expensive, but we can optimize it later
    distances = [(sum([np.dot(vertices[face[i]], light_vector) for i in range(len(face))])/3, face) for face in faces]
    distances.sort(reverse=True)

    for _, face in distances:
        ...

ezgif com-video-to-gif (3)

now lets try drawing those edges and vertices too:

ezgif com-video-to-gif (4)

and thats all for this post! we have some blender default cube spinning in nowhere in the end, heehoo!

as always, find the complete code here