= 3d Box This is the first demo which will use real 3d math to do projection into 2d. The code will be presented as a monolithic file but it is intended that it be split into many files such as putting Object3d into it's own file, and then moving functions like rotate_x into there (maybe using staticmethod) or into their own library class. Object data could also stand to be moved out, maybe into an objects.py sort of class, or into files like cube.obj (i.e. cube.txt) which could be read in at runtime. == The Code (Wireframe) import pygame import sys import math # Initialize Pygame pygame.init() # Constants WIDTH, HEIGHT = 800, 600 FOV = 256 # Field of view CAMERA_Z = 4 # Camera distance from the object SQUARE_SIZE = 1 # Half the size of the square for vertex calculations rotation_angles = [0, 0, 0] # Rotation angles for x, y, z # Set up the display screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Box") # Square square_obj = [ [ # Back face [-SQUARE_SIZE, -SQUARE_SIZE, 0], # Bottom left [SQUARE_SIZE, -SQUARE_SIZE, 0], # Bottom right [SQUARE_SIZE, SQUARE_SIZE, 0], # Top right [-SQUARE_SIZE, SQUARE_SIZE, 0] # Top left ] ] # Define the vertices of a 3D cube cube_obj = [ [ # Back face (-SQUARE_SIZE, -SQUARE_SIZE, -SQUARE_SIZE), # Bottom left (SQUARE_SIZE, -SQUARE_SIZE, -SQUARE_SIZE), # Bottom right (SQUARE_SIZE, SQUARE_SIZE, -SQUARE_SIZE), # Top right (-SQUARE_SIZE, SQUARE_SIZE, -SQUARE_SIZE) # Top left ], [ # Front face (-SQUARE_SIZE, -SQUARE_SIZE, SQUARE_SIZE), # Bottom left (SQUARE_SIZE, -SQUARE_SIZE, SQUARE_SIZE), # Bottom right (SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE), # Top right (-SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE) # Top left ], [ # Left face (-SQUARE_SIZE, -SQUARE_SIZE, -SQUARE_SIZE), # Bottom left (-SQUARE_SIZE, -SQUARE_SIZE, SQUARE_SIZE), # Bottom right (-SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE), # Top right (-SQUARE_SIZE, SQUARE_SIZE, -SQUARE_SIZE) # Top left ], [ # Right face (SQUARE_SIZE, -SQUARE_SIZE, -SQUARE_SIZE), # Bottom left (SQUARE_SIZE, -SQUARE_SIZE, SQUARE_SIZE), # Bottom right (SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE), # Top right (SQUARE_SIZE, SQUARE_SIZE, -SQUARE_SIZE) # Top left ], [ # Top face (-SQUARE_SIZE, SQUARE_SIZE, -SQUARE_SIZE), # Bottom left (SQUARE_SIZE, SQUARE_SIZE, -SQUARE_SIZE), # Bottom right (SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE), # Top right (-SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE) # Top left ], [ # Bottom face (-SQUARE_SIZE, -SQUARE_SIZE, -SQUARE_SIZE), # Bottom left (SQUARE_SIZE, -SQUARE_SIZE, -SQUARE_SIZE), # Bottom right (SQUARE_SIZE, -SQUARE_SIZE, SQUARE_SIZE), # Top right (-SQUARE_SIZE, -SQUARE_SIZE, SQUARE_SIZE) # Top left ] ] # Function to project 3D points to 2D def project(point): x, y, z = point scale = FOV / (CAMERA_Z + z) x_proj = int(x * scale + WIDTH // 2) y_proj = int(-y * scale + HEIGHT // 2) return (x_proj, y_proj) # Functions for rotating points def rotate_x(point, angle): rad = math.radians(angle) cos_angle = math.cos(rad) sin_angle = math.sin(rad) x, y, z = point y_rotated = y * cos_angle - z * sin_angle z_rotated = y * sin_angle + z * cos_angle return (x, y_rotated, z_rotated) def rotate_y(point, angle): rad = math.radians(angle) cos_angle = math.cos(rad) sin_angle = math.sin(rad) x, y, z = point x_rotated = z * sin_angle + x * cos_angle z_rotated = z * cos_angle - x * sin_angle return (x_rotated, y, z_rotated) def rotate_z(point, angle): rad = math.radians(angle) cos_angle = math.cos(rad) sin_angle = math.sin(rad) x, y, z = point x_rotated = x * cos_angle - y * sin_angle y_rotated = x * sin_angle + y * cos_angle return (x_rotated, y_rotated, z) # Function to draw the cube using faces def draw_obj(position, faces): face_data = [] for index, face in enumerate(faces): transformed_vertices = [] # Transform each vertex for vertex in face: # Rotate the vertex around the Z-axis vertex = rotate_z(vertex, rotation_angles[2]) # Rotate the result around the Y-axis vertex = rotate_y(vertex, rotation_angles[1]) # Rotate the result around the X-axis vertex = rotate_x(vertex, rotation_angles[0]) # Append the transformed vertex transformed_vertices.append(vertex) projected_vertices = [] # Project each transformed vertex to 2D for vertex in transformed_vertices: projected_vertex = project((vertex[0] + position[0], vertex[1] + position[1], vertex[2] + position[2])) projected_vertices.append(projected_vertex) pygame.draw.polygon(screen, color, projected_vertices) # Fill the polygon with color def draw_obj(position, faces): for face in faces: transformed_vertices = [ rotate_x(rotate_y(rotate_z(vertex, rotation_angles[2]), rotation_angles[1]), rotation_angles[0]) for vertex in face ] projected_vertices = [project((x + position[0], y + position[1], z + position[2])) for (x, y, z) in transformed_vertices] # Draw the polygon face pygame.draw.polygon(screen, "white", projected_vertices, True) # Main loop def main(): clock = pygame.time.Clock() position = [0, 0, 0] # Initial position of the cube while True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() keys = pygame.key.get_pressed() # Movement controls if keys[pygame.K_UP]: # Move forward along Z position[2] += 0.1 if keys[pygame.K_DOWN]: # Move backward along Z position[2] -= 0.1 if keys[pygame.K_a]: # Move left along X position[0] -= 0.1 if keys[pygame.K_d]: # Move right along X position[0] += 0.1 if keys[pygame.K_w]: # Move up along Y position[1] += 0.1 if keys[pygame.K_s]: # Move down along Y position[1] -= 0.1 # Rotation controls if keys[pygame.K_1]: # Rotate around X-axis rotation_angles[0] += 1 if keys[pygame.K_2]: # Rotate around X-axis rotation_angles[0] -= 1 if keys[pygame.K_3]: # Rotate around Y-axis rotation_angles[1] += 1 if keys[pygame.K_4]: # Rotate around Y-axis rotation_angles[1] -= 1 if keys[pygame.K_5]: # Rotate around Z-axis rotation_angles[2] += 1 if keys[pygame.K_6]: # Rotate around Z-axis rotation_angles[2] -= 1 # Fill the screen with black screen.fill((0, 0, 0)) # Draw the cube using polygons draw_obj(position, cube_obj) # Draw the bounding box (optional) pygame.draw.rect(screen, "red", (0, 0, WIDTH, HEIGHT), 1) # Update the display pygame.display.flip() clock.tick(60) if __name__ == "__main__": main() == The Code (with Simple Z-avg and fill) import pygame import sys import math # Initialize Pygame pygame.init() # Constants WIDTH, HEIGHT = 800, 600 FOV = 256 # Field of view CAMERA_Z = 4 # Camera distance from the object SQUARE_SIZE = 1 # Half the size of the square for vertex calculations rotation_angles = [0, 0, 0] # Rotation angles for x, y, z # Set up the display screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("3D Cube with Polygons") # Define colors for each face face_colors = [ (255, 0, 0), # Red for Back face (0, 255, 0), # Green for Front face (0, 0, 255), # Blue for Left face (255, 255, 0), # Yellow for Right face (255, 0, 255), # Magenta for Top face (0, 255, 255) # Cyan for Bottom face ] # Object3D class to store vertices and position class Object3D: def __init__(self, vertices, position=(0, 0, 0)): self.vertices = vertices self.position = list(position) self.rotation = [0, 0, 0] # Rotation angles for x, y, z def rotate(self, angle_x, angle_y, angle_z): self.rotation[0] += angle_x self.rotation[1] += angle_y self.rotation[2] += angle_z def move(self, dx, dy, dz): self.position[0] += dx self.position[1] += dy self.position[2] += dz def get_transformed_vertices(self): transformed_faces = [] for face in self.vertices: transformed_face = [ rotate_x(rotate_y(rotate_z(vertex, self.rotation[2]), self.rotation[1]), self.rotation[0]) for vertex in face ] transformed_faces.append(transformed_face) return transformed_faces # Define the vertices of a 3D cube cube_vertices = [ [ # Back face (-SQUARE_SIZE, -SQUARE_SIZE, -SQUARE_SIZE), (SQUARE_SIZE, -SQUARE_SIZE, -SQUARE_SIZE), (SQUARE_SIZE, SQUARE_SIZE, -SQUARE_SIZE), (-SQUARE_SIZE, SQUARE_SIZE, -SQUARE_SIZE) ], [ # Front face (-SQUARE_SIZE, -SQUARE_SIZE, SQUARE_SIZE), (SQUARE_SIZE, -SQUARE_SIZE, SQUARE_SIZE), (SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE), (-SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE) ], [ # Left face (-SQUARE_SIZE, -SQUARE_SIZE, -SQUARE_SIZE), (-SQUARE_SIZE, -SQUARE_SIZE, SQUARE_SIZE), (-SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE), (-SQUARE_SIZE, SQUARE_SIZE, -SQUARE_SIZE) ], [ # Right face (SQUARE_SIZE, -SQUARE_SIZE, -SQUARE_SIZE), (SQUARE_SIZE, -SQUARE_SIZE, SQUARE_SIZE), (SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE), (SQUARE_SIZE, SQUARE_SIZE, -SQUARE_SIZE) ], [ # Top face (-SQUARE_SIZE, SQUARE_SIZE, -SQUARE_SIZE), (SQUARE_SIZE, SQUARE_SIZE, -SQUARE_SIZE), (SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE), (-SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE) ], [ # Bottom face (-SQUARE_SIZE, -SQUARE_SIZE, -SQUARE_SIZE), (SQUARE_SIZE, -SQUARE_SIZE, -SQUARE_SIZE), (SQUARE_SIZE, -SQUARE_SIZE, SQUARE_SIZE), (-SQUARE_SIZE, -SQUARE_SIZE, SQUARE_SIZE) ] ] # Function to project 3D points to 2D def project(point): x, y, z = point scale = FOV / (CAMERA_Z + z) x_proj = int(x * scale + WIDTH // 2) y_proj = int(-y * scale + HEIGHT // 2) return (x_proj, y_proj) # Functions for rotating points def rotate_x(point, angle): rad = math.radians(angle) cos_angle = math.cos(rad) sin_angle = math.sin(rad) x, y, z = point y_rotated = y * cos_angle - z * sin_angle z_rotated = y * sin_angle + z * cos_angle return (x, y_rotated, z_rotated) def rotate_y(point, angle): rad = math.radians(angle) cos_angle = math.cos(rad) sin_angle = math.sin(rad) x, y, z = point x_rotated = z * sin_angle + x * cos_angle z_rotated = z * cos_angle - x * sin_angle return (x_rotated, y, z_rotated) def rotate_z(point, angle): rad = math.radians(angle) cos_angle = math.cos(rad) sin_angle = math.sin(rad) x, y, z = point x_rotated = x * cos_angle - y * sin_angle y_rotated = x * sin_angle + y * cos_angle return (x_rotated, y_rotated, z) # Function to draw the cube using faces def draw_obj(obj): face_data = [] transformed_faces = obj.get_transformed_vertices() for index, face in enumerate(transformed_faces): projected_vertices = [project((x + obj.position[0], y + obj.position[1], z + obj.position[2])) for (x, y, z) in face] # Calculate average Z value for sorting avg_z = sum(vertex[2] for vertex in face) / len(face) face_data.append((avg_z, projected_vertices, face_colors[index])) # Sort faces by average Z value (back to front) face_data.sort(key=lambda x: x[0], reverse=True) # Draw each face in sorted order for _, projected_vertices, color in face_data: pygame.draw.polygon(screen, color, projected_vertices, False) # Main loop def main(): clock = pygame.time.Clock() # Create cube object cube = Object3D(cube_vertices) while True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() keys = pygame.key.get_pressed() # Movement controls if keys[pygame.K_w]: # Move forward along Z cube.move(0, 0, 0.1) if keys[pygame.K_s]: # Move backward along Z cube.move(0, 0, -0.1) if keys[pygame.K_a]: # Move left along X cube.move(-0.1, 0, 0) if keys[pygame.K_d]: # Move right along X cube.move(0.1, 0, 0) if keys[pygame.K_UP]: # Move up along Y cube.move(0, 0.1, 0) if keys[pygame.K_DOWN]: # Move down along Y cube.move(0, -0.1, 0) # Rotation controls if keys[pygame.K_1]: # Rotate around X-axis cube.rotate(1, 0, 0) if keys[pygame.K_2]: # Rotate around X-axis cube.rotate(-1, 0, 0) if keys[pygame.K_3]: # Rotate around Y-axis cube.rotate(0, 1, 0) if keys[pygame.K_4]: # Rotate around Y-axis cube.rotate(0, -1, 0) if keys[pygame.K_5]: # Rotate around Z-axis cube.rotate(0, 0, 1) if keys[pygame.K_6]: # Rotate around Z-axis cube.rotate(0, 0, -1) # Fill the screen with black screen.fill((0, 0, 0)) # Draw the cube draw_obj(cube) # Draw the bounding box (optional) pygame.draw.rect(screen, (255, 0, 0), (WIDTH // 2 - 400, HEIGHT // 2 - 300, 800, 600), 1) # Update the display pygame.display.flip() clock.tick(60) if __name__ == "__main__": main() === Adding Scaling class Object3D: def __init__(self, vertices, position=(0, 0, 0)): self.vertices = vertices self.position = list(position) self.rotation = [0, 0, 0] # Rotation angles for x, y, z self.scale_factors = [1, 1, 1] # Scaling factors for x, y, z def rotate(self, angle_x, angle_y, angle_z): self.rotation[0] += angle_x self.rotation[1] += angle_y self.rotation[2] += angle_z def move(self, dx, dy, dz): self.position[0] += dx self.position[1] += dy self.position[2] += dz def scale(self, scale_x, scale_y, scale_z): """Set the scaling factors for the object.""" self.scale_factors = [scale_x, scale_y, scale_z] def get_transformed_vertices(self): transformed_faces = [] for face in self.vertices: transformed_face = [ rotate_x( rotate_y( rotate_z( (x * self.scale_factors[0], y * self.scale_factors[1], z * self.scale_factors[2]), self.rotation[2]), self.rotation[1]), self.rotation[0]) for (x, y, z) in face ] transformed_faces.append(transformed_face) return transformed_faces