3d_box_v1
Table of Contents
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
3d_box_v1.txt · Last modified: 2024/09/21 01:13 by appledog
