add to my game a SkyBox class, to make a skybox using 6 images:

import numpy as np
import pygame

class BasicMeshMaterial:
    def __init__(self, color, obj):
        self.color = color  # Color in RGB format, e.g., (255, 0, 0) for red
        self.object = obj

    def draw_material(self, screen, screen_width, screen_height, fov=256, viewer_distance=5):
        # Rotate and project vertices
        vertices = self.object.rotate(self.object.angle_x, self.object.angle_y, self.object.angle_z)
        projected_vertices = [self.object.project(v, screen_width, screen_height, fov, viewer_distance) for v in vertices]

        # Exclude vertices that can't be projected
        if all(pv is not None for pv in projected_vertices):
            self.draw_faces(screen, projected_vertices)

    def draw_faces(self, screen, projected_vertices):
        faces = self.object.get_faces()  # Get faces from object
        for face in faces:
            pygame.draw.polygon(screen, self.color, [projected_vertices[i] for i in face])

class OutlineMaterial:
    def __init__(self, color, obj, line_width=1):
        self.color = color
        self.object = obj
        self.line_width = line_width

    def draw_material(self, screen, screen_width, screen_height, fov=256, viewer_distance=5):
        # Rotate and project vertices
        vertices = self.object.rotate(self.object.angle_x, self.object.angle_y, self.object.angle_z)
        projected_vertices = [self.object.project(v, screen_width, screen_height, fov, viewer_distance) for v in vertices]

        # Exclude vertices that can't be projected
        if all(pv is not None for pv in projected_vertices):
            self.draw_edges(screen, projected_vertices)

    def draw_edges(self, screen, projected_vertices):
        edges = self.object.get_edges()  # Get edges from object
        for edge in edges:
            pygame.draw.line(screen, self.color, projected_vertices[edge[0]], projected_vertices[edge[1]], self.line_width)

class Cube:
    def __init__(self, x, y, z, width, height, depth, material=None):
        self.position = np.array([x, y, z])
        self.width = width
        self.height = height
        self.depth = depth
        self.angle_x = 0
        self.angle_y = 0
        self.angle_z = 0
        self.material = material if material else BasicMeshMaterial((255, 255, 255), self)  # default white material

    def setPosition(self, new_position):
        self.position = np.array(new_position)

    def getVertices(self):
        x, y, z = self.position
        return [
            [x, y, z],
            [x + self.width, y, z],
            [x + self.width, y + self.height, z],
            [x, y + self.height, z],
            [x, y, z + self.depth],
            [x + self.width, y, z + self.depth],
            [x + self.width, y + self.height, z + self.depth],
            [x, y + self.height, z + self.depth]
        ]

    def get_faces(self):
        return [
            (0, 1, 2, 3),  # Front
            (4, 5, 6, 7),  # Back
            (0, 3, 7, 4),  # Left
            (1, 2, 6, 5),  # Right
            (3, 2, 6, 7),  # Top
            (0, 1, 5, 4)   # Bottom
        ]

    def get_edges(self):
        return [
            (0, 1), (1, 2), (2, 3), (3, 0),  # Front edges
            (4, 5), (5, 6), (6, 7), (7, 4),  # Back edges
            (0, 4), (1, 5), (2, 6), (3, 7)   # Connecting edges
        ]

    def rotate(self, angle_x, angle_y, angle_z):
        cos_x, sin_x = np.cos(angle_x), np.sin(angle_x)
        cos_y, sin_y = np.cos(angle_y), np.sin(angle_y)
        cos_z, sin_z = np.cos(angle_z), np.sin(angle_z)

        rotation_x = np.array([
            [1, 0, 0],
            [0, cos_x, -sin_x],
            [0, sin_x, cos_x]
        ])

        rotation_y = np.array([
            [cos_y, 0, sin_y],
            [0, 1, 0],
            [-sin_y, 0, cos_y]
        ])

        rotation_z = np.array([
            [cos_z, -sin_z, 0],
            [sin_z, cos_z, 0],
            [0, 0, 1]
        ])

        rotation_matrix = rotation_x @ rotation_y @ rotation_z
        rotated_vertices = []
        for vertex in self.getVertices():
            rotated_vertex = np.dot(rotation_matrix, np.array(vertex) - self.position) + self.position
            rotated_vertices.append(rotated_vertex)
    
        return rotated_vertices

    def project(self, vertex, screen_width, screen_height, fov, viewer_distance, epsilon=0.1):
        z = vertex[2] + epsilon
        if z <= 0:
            return None  # Ignore vertices "behind the camera"
        
        factor = fov / z
        x = vertex[0] * factor + screen_width / 2
        y = -vertex[1] * factor + screen_height / 2
        return (int(x), int(y))

    def draw(self, screen, screen_width, screen_height, fov=256, viewer_distance=5):
        self.material.draw_material(screen, screen_width, screen_height, fov, viewer_distance)

class Pyramid:
    def __init__(self, x, y, z, width, height, depth, material=None):
        self.position = np.array([x, y, z])
        self.width = width
        self.height = height
        self.depth = depth
        self.angle_x = 0
        self.angle_y = 0
        self.angle_z = 0
        self.material = material if material else BasicMeshMaterial((255, 255, 255), self)  # Default white material

    def setPosition(self, new_position):
        self.position = np.array(new_position)

    def getVertices(self):
        x, y, z = self.position
        return [
            [x, y, z],  # Vertex 0 (bottom left front)
            [x + self.width, y, z],  # Vertex 1 (bottom right front)
            [x + self.width, y, z + self.depth],  # Vertex 2 (bottom right back)
            [x, y, z + self.depth],  # Vertex 3 (bottom left back)
            [(x + x + self.width) / 2, y + self.height, (z + z + self.depth) / 2]  # Vertex 4 (pyramid peak)
        ]

    def get_faces(self):
        return [
            (0, 1, 4),  # Front
            (1, 2, 4),  # Right
            (2, 3, 4),  # Back
            (3, 0, 4),  # Left
            (0, 1, 2, 3)  # Bottom
        ]

    def get_edges(self):
        return [
            (0, 1), (1, 2), (2, 3), (3, 0),  # Base edges
            (0, 4), (1, 4), (2, 4), (3, 4)   # Side edges
        ]

    def rotate(self, angle_x, angle_y, angle_z):
        cos_x, sin_x = np.cos(angle_x), np.sin(angle_x)
        cos_y, sin_y = np.cos(angle_y), np.sin(angle_y)
        cos_z, sin_z = np.cos(angle_z), np.sin(angle_z)

        rotation_x = np.array([
            [1, 0, 0],
            [0, cos_x, -sin_x],
            [0, sin_x, cos_x]
        ])

        rotation_y = np.array([
            [cos_y, 0, sin_y],
            [0, 1, 0],
            [-sin_y, 0, cos_y]
        ])

        rotation_z = np.array([
            [cos_z, -sin_z, 0],
            [sin_z, cos_z, 0],
            [0, 0, 1]
        ])

        rotation_matrix = rotation_x @ rotation_y @ rotation_z
        rotated_vertices = []
        for vertex in self.getVertices():
            rotated_vertex = np.dot(rotation_matrix, np.array(vertex) - self.position) + self.position
            rotated_vertices.append(rotated_vertex)
        
        return rotated_vertices

    def project(self, vertex, screen_width, screen_height, fov, viewer_distance, epsilon=0.1):
        z = vertex[2] + epsilon
        if z <= 0:
            return None  # Ignore vertices "behind the camera"

        factor = fov / z
        x = vertex[0] * factor + screen_width / 2
        y = -vertex[1] * factor + screen_height / 2
        return (int(x), int(y))

    def draw(self, screen, screen_width, screen_height, fov=256, viewer_distance=5):
        self.material.draw_material(screen, screen_width, screen_height, fov, viewer_distance)

class Plane:
    def __init__(self, x, y, z, width, depth, material=None):
        self.position = np.array([x, y, z])
        self.width = width
        self.depth = depth
        self.angle_x = 0
        self.angle_y = 0
        self.angle_z = 0
        self.material = material if material else BasicMeshMaterial((255, 255, 255), self)  # Default white material

    def setPosition(self, new_position):
        self.position = np.array(new_position)

    def getVertices(self):
        x, y, z = self.position
        return [
            [x, y, z],
            [x + self.width, y, z],
            [x + self.width, y, z + self.depth],
            [x, y, z + self.depth]
        ]

    def get_faces(self):
        return [(0, 1, 2, 3)] 

    def get_edges(self):
        return [
            (0, 1), (1, 2), (2, 3), (3, 0) 
        ]

    def rotate(self, angle_x, angle_y, angle_z):
        cos_x, sin_x = np.cos(angle_x), np.sin(angle_x)
        cos_y, sin_y = np.cos(angle_y), np.sin(angle_y)
        cos_z, sin_z = np.cos(angle_z), np.sin(angle_z)

        rotation_x = np.array([
            [1, 0, 0],
            [0, cos_x, -sin_x],
            [0, sin_x, cos_x]
        ])

        rotation_y = np.array([
            [cos_y, 0, sin_y],
            [0, 1, 0],
            [-sin_y, 0, cos_y]
        ])

        rotation_z = np.array([
            [cos_z, -sin_z, 0],
            [sin_z, cos_z, 0],
            [0, 0, 1]
        ])

        rotation_matrix = rotation_x @ rotation_y @ rotation_z
        rotated_vertices = []
        for vertex in self.getVertices():
            rotated_vertex = np.dot(rotation_matrix, np.array(vertex) - self.position) + self.position
            rotated_vertices.append(rotated_vertex)

        return rotated_vertices

    def project(self, vertex, screen_width, screen_height, fov, viewer_distance, epsilon=0.1):
        z = vertex[2] + epsilon
        if z <= 0:
            return None  

        factor = fov / z
        x = vertex[0] * factor + screen_width / 2
        y = -vertex[1] * factor + screen_height / 2
        return (int(x), int(y))

    def draw(self, screen, screen_width, screen_height, fov=256, viewer_distance=5):
        self.material.draw_material(screen, screen_width, screen_height, fov, viewer_distance)


class Sphere:
    def __init__(self, x, y, z, radius, sectors, stacks, material=None):
        self.position = np.array([x, y, z])
        self.radius = radius
        self.sectors = sectors  # Horizontal segments (longitude)
        self.stacks = stacks    # Vertical segments (latitude)
        self.angle_x = 0
        self.angle_y = 0
        self.angle_z = 0
        self.material = material if material else BasicMeshMaterial((255, 255, 255), self)  # Default white material

        # Generate vertices and faces
        self.vertices, self.faces = self.generate_sphere()

    def generate_sphere(self):
        vertices = []
        faces = []

        # Generate vertices
        for stack in range(self.stacks + 1):
            stack_angle = np.pi / 2 - stack * np.pi / self.stacks  # from pi/2 to -pi/2
            xy = self.radius * np.cos(stack_angle)
            z = self.radius * np.sin(stack_angle)

            for sector in range(self.sectors + 1):
                sector_angle = sector * 2 * np.pi / self.sectors  # 0 to 2pi
                x = xy * np.cos(sector_angle)
                y = xy * np.sin(sector_angle)
                vertices.append([x + self.position[0], y + self.position[1], z + self.position[2]])

        # Generate faces
        for stack in range(self.stacks):
            for sector in range(self.sectors):
                first = (stack * (self.sectors + 1)) + sector
                second = first + self.sectors + 1

                faces.append((first, second, first + 1))
                faces.append((second, second + 1, first + 1))

        return vertices, faces

    def rotate(self, angle_x, angle_y, angle_z):
        cos_x, sin_x = np.cos(angle_x), np.sin(angle_x)
        cos_y, sin_y = np.cos(angle_y), np.sin(angle_y)
        cos_z, sin_z = np.cos(angle_z), np.sin(angle_z)

        rotation_x = np.array([
            [1, 0, 0],
            [0, cos_x, -sin_x],
            [0, sin_x, cos_x]
        ])

        rotation_y = np.array([
            [cos_y, 0, sin_y],
            [0, 1, 0],
            [-sin_y, 0, cos_y]
        ])

        rotation_z = np.array([
            [cos_z, -sin_z, 0],
            [sin_z, cos_z, 0],
            [0, 0, 1]
        ])

        rotation_matrix = rotation_x @ rotation_y @ rotation_z
        rotated_vertices = []
        for vertex in self.vertices:
            rotated_vertex = np.dot(rotation_matrix, np.array(vertex) - self.position) + self.position
            rotated_vertices.append(rotated_vertex)
        
        return rotated_vertices

    def project(self, vertex, screen_width, screen_height, fov, viewer_distance, epsilon=0.1):
        z = vertex[2] + epsilon
        if z <= 0:
            return None  # Ignore vertices "behind the camera"

        factor = fov / z
        x = vertex[0] * factor + screen_width / 2
        y = -vertex[1] * factor + screen_height / 2
        return (int(x), int(y))

    def get_faces(self):
        return self.faces

    def get_edges(self):
        # For a sphere, we don't need edges for rendering purposes in this context
        return []

    def draw(self, screen, screen_width, screen_height, fov=256, viewer_distance=5):
        self.material.draw_material(screen, screen_width, screen_height, fov, viewer_distance)