Using vector cross product to hide 3D faces

Question:

The code creates 3D coordinates for the cube and then displays them on the 2D screen but you can still see the back faces of the cube. What is the best method for hiding these faces?

picture of the output

new output

new output2

points = [(-1,-1,-1),(-1,-1,1),(-1,1,1),(-1,1,-1),
          (1,-1,-1),(1,-1,1),(1,1,1),(1,1,-1), ]#coords for points

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

These are the coordinates of the points and which points should be joined to which

def flattenPoint(point):
    (x, y, z) = (point[0], point[1], point[2])
    xnew = x#x axis rotation
    ynew = y * math.cos(rotatedanglex) - z * math.sin(rotatedanglex)
    znew = y * math.sin(rotatedanglex) + z * math.cos(rotatedanglex)

    xnew = znew * math.sin(rotatedangley) + xnew * math.cos(rotatedangley)
    ynew = ynew #y axis rotation
    znew = ynew * math.cos(rotatedangley) - xnew * math.sin(rotatedangley)
    
    
    projectedY = int(height / 2 + ((ynew * distance) / (znew + distance)) * scale)
    projectedX = int(width / 2 + ((xnew * distance) / (znew + distance)) * scale)
    return (projectedX, projectedY, znew)

def createOutline(points):
    a, b, c, d = points[0], points[1], points[2], points[3]
    coords = ((b[0], b[1]), (a[0], a[1]), (d[0], d[1]),(c[0], c[1]))
    pygame.draw.polygon(screen, blue, coords, 1)

”’
The FlattenPoint function rotates the 3D points and then turns them into 2D coordinates that are displayed.
”’

def createFace(points):
    a, b, c, d = points[0], points[1], points[2], points[3]
    coords = ((b[0], b[1]), (a[0], a[1]), (d[0], d[1]),(c[0], c[1]))
    pygame.draw.polygon(screen, green, coords)
    

createFace joins up the 2D coordinates.

def render(points, faces):
        coords = []
        for point in points:
            coords.append(flattenPoint(point))
        screen.fill(screencolour)

        
        
        
        for face in faces:
           
            createFace((coords[face[0]], coords[face[1]], coords[face[2]], coords[face[3]]))
        
        for face in faces:#must draw outline after all the faces have been drawn
            createOutline((coords[face[0]], coords[face[1]], coords[face[2]],coords[face[3]]))

”’

Asked By: Alex

||

Answers:

Compute the normal vector to of a face and cull the faces where the normal vector points away from the view. The normal vector can be computed with the Cross product:

def cross(a, b):
    return [a[1]*b[2] - a[2]*b[1], a[2]*b[0] - a[0]*b[2], a[0]*b[1] - a[1]*b[0]]

Use the cross product and cull the faces:

def createFace(points):
    a, b, c, d = points[0], points[1], points[2], points[3]

    v1 = b[0]-a[0], b[1]-a[1], b[2]-a[2]
    v2 = c[0]-a[0], c[1]-a[1], c[2]-a[2]
    n = cross(v1, v2)
    if n[2] < 0:
        return

    coords = ((b[0], b[1]), (a[0], a[1]), (d[0], d[1]),(c[0], c[1]))
    pygame.draw.polygon(screen, green, coords)

You have to ensure that the winding order of all the faces in counter clockwise. See also Face Culling and Back-face culling

Change the vertices and indices as follows:

points = [(-1,-1,-1),( 1,-1,-1), (1, 1,-1),(-1, 1,-1),
          (-1,-1, 1),( 1,-1, 1), (1, 1, 1),(-1, 1, 1)]

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

However, I recommend to implement a depth test. See Pygame rotating cubes around axis and Does PyGame do 3d?.


Complete example:

import pygame
import math

points = [(-1,-1,-1),( 1,-1,-1), (1, 1,-1),(-1, 1,-1),
          (-1,-1, 1),( 1,-1, 1), (1, 1, 1),(-1, 1, 1)]

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

def flattenPoint(point):
    (x, y, z) = (point[0], point[1], point[2])
    xnew = x#x axis rotation
    ynew = y * math.cos(rotatedanglex) - z * math.sin(rotatedanglex)
    znew = y * math.sin(rotatedanglex) + z * math.cos(rotatedanglex)

    xnew = znew * math.sin(rotatedangley) + xnew * math.cos(rotatedangley)
    ynew = ynew #y axis rotation
    znew = ynew * math.cos(rotatedangley) - xnew * math.sin(rotatedangley)
    
    
    projectedY = int(height / 2 + ((ynew * distance) / (znew + distance)) * scale)
    projectedX = int(width / 2 + ((xnew * distance) / (znew + distance)) * scale)
    return (projectedX, projectedY, znew)

def cross(a, b):
    return [a[1]*b[2] - a[2]*b[1], a[2]*b[0] - a[0]*b[2], a[0]*b[1] - a[1]*b[0]]

def createOutline(points):
    a, b, c, d = points[0], points[1], points[2], points[3]

    v1 = b[0]-a[0], b[1]-a[1], b[2]-a[2]
    v2 = c[0]-a[0], c[1]-a[1], c[2]-a[2]
    n = cross(v1, v2)
    if n[2] < 0:
        return

    coords = ((b[0], b[1]), (a[0], a[1]), (d[0], d[1]),(c[0], c[1]))
    pygame.draw.polygon(screen, blue, coords, 3)

def createFace(points):
    a, b, c, d = points[0], points[1], points[2], points[3]

    v1 = b[0]-a[0], b[1]-a[1], b[2]-a[2]
    v2 = c[0]-a[0], c[1]-a[1], c[2]-a[2]
    n = cross(v1, v2)
    if n[2] < 0:
        return

    coords = ((b[0], b[1]), (a[0], a[1]), (d[0], d[1]),(c[0], c[1]))
    pygame.draw.polygon(screen, green, coords)

def render(points, faces):
        coords = []
        for point in points:
            coords.append(flattenPoint(point))
        screen.fill(screencolour)
        for face in faces:
            createFace((coords[face[0]], coords[face[1]], coords[face[2]], coords[face[3]]))
        for face in faces:#must draw outline after all the faces have been drawn
            createOutline((coords[face[0]], coords[face[1]], coords[face[2]],coords[face[3]]))

pygame.init()
screen = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()

rotatedanglex = 0.0
rotatedangley = 0.0
width, height = screen.get_size()
distance = 200.0
scale = 75.0
green = (0, 255, 0)
blue = (0, 0, 255)
screencolour = (0, 0, 0)

run = True
while run:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False          

    screen.fill(0)
    render(points, faces)
    pygame.display.flip()
    rotatedanglex += 0.01
    rotatedangley += 0.02

pygame.quit()
exit()
Answered By: Rabbid76
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.