Newer
Older
3d_renderer / main.py
@cory cory on 20 Mar 2022 11 KB factor out classes
# TODO
# make todo list

# import
import colorsys

import pygame
from functions import *

# variables
# screen size in pixels
from model import Colour, Point3D, Face, Cube

size = (1000, 1000)
# screen with in units
scrWidth = 3.2

# player information
playerPos: Point3D = Point3D([0, 10, 0])
playerSpeed: Point3D = Point3D([0, 0, 0])

# todo implement these variables into the code
acc = 20  # units per second per second
maxSpeed = 4  # units per second
sensitivity = 0.05


frameRate = 60  # measures the frame-rate (if this decreases then other things are sped up)

# program variables
running = True
triggerDraw = True
placing = True
blockDistance = 3

lastMillis = [0, 0, 0]
scale = size[1] / scrWidth
allFaces: list[Face] = []

# colours
bgColour: Colour = Colour([0, 0, 0])
fgColour: Colour = Colour([255, 255, 255])

# placing colours is to be replaced with placingTextures
placingColours: list[list[Colour]] = [[Colour([100, 100, 100]), Colour([100, 100, 100]), Colour([100, 100, 100]), Colour([100, 100, 100]), Colour([100, 100, 100]), Colour([100, 100, 100])],
                                      [Colour([162, 84, 57]), Colour([162, 84, 57]), Colour([162, 84, 57]), Colour([162, 84, 57]), Colour([162, 84, 57]), Colour([162, 84, 57])],
                                      [Colour([50, 255, 50]), Colour([100, 255, 100]), Colour([100, 255, 100]), Colour([100, 255, 100]), Colour([100, 255, 100]), Colour([162, 84, 57])]]

currentTexture: int = 0

# create cubes
cubes: list[Cube] = []
for x in [[-12, 3, 4], [2, 2, 2], [2, 0, 0], [0, 2, 0], [0, 0, 0], [6, -8, 4]]:
    cubes.append(Cube(Point3D(x), [Colour([255, 0, 0]) for x in range(6)], True, True))

# todo update or remove
# axes points
axesPoints = [[1000, 0, 0], [-1000, 0, 0], [0, 1000, 0], [0, -1000, 0], [0, 0, 1000], [0, 0, -1000]]
axesEdges = [[0, 1], [2, 3], [4, 5]]
axesColours = [(0, .8, .8), (.2, .8, 1), (.8, .8, .8)]
i = 0
for c in axesColours:
    axesColours[i] = colorsys.hsv_to_rgb(c[0], c[1], c[2])
    axesColours[i] = tuple(255 * x for x in axesColours[i])
    i += 1

# rendering variables
# rotation
rotY = 0
rotX = 0
rotZ = 0

# distance
camDistance = 0
# fov
camFOV = 120

# matrices
camMatrix: numpy.array = generate_cam_matrix(rotX, rotY, rotZ, playerPos)

FPDis = fov_calc(camFOV, scrWidth)

# start
pygame.init()
lastMillis[1] = pygame.time.get_ticks() + 1000
lastMillis[2] = pygame.time.get_ticks() + 1000
timerOffset = 0
screen = pygame.display.set_mode(size)
screen.fill(bgColour.get_rgb())

# initial values for variables so pycharm doesn't get mad
keys = pygame.key.get_pressed()
newCubePos: Point3D = Point3D([0, 0, 0])

# settings
pygame.mouse.set_visible(True)
grabbed = False
pygame.event.set_grab(False)

# create surfaces
mainSurface = pygame.Surface(size)

while running:
    # every tick:
    if lastMillis[1] + 16.6 <= pygame.time.get_ticks() or triggerDraw:
        frameRate = 1 / ((pygame.time.get_ticks() - lastMillis[1]) / 1000)
        # change colours and apply rotation from mouse

        if grabbed:
            # rotate camera if mouse moves
            dZ, dX = pygame.mouse.get_rel()
            rotX -= dX * sensitivity
            rotZ += dZ * sensitivity

            # fix camera within movement bounds
            rotZ = rotZ % 360
            if rotX > 90:
                rotX = 90
            elif rotX < -90:
                rotX = -90

        if not triggerDraw:
            lastMillis[1] = pygame.time.get_ticks()
        triggerDraw = False

        # reset surface area
        mainSurface.fill(fgColour.get_rgb())

        # save currently pressed keys
        keys = pygame.key.get_pressed()

        # generate matrices
        camMatrix = generate_cam_matrix(rotX, rotY, rotZ, playerPos)

        # __draw cubes__
        # calculate 3d and project 3d point locations
        for cube in cubes:
            for face in cube.get_faces():
                allFaces.append(face)

            # if cube.has_texture():
            #     texturedFaces = cube.get_textured_faces(playerPos)
            #     for texturedFace in texturedFaces:
            #         newSubFaces = []
            #         try:
            #             faces = texturedFace.get_sub_faces()
            #             for face in faces:
            #                 newPoints = fix_points_behind_camera(rotate(face.points, rotX, rotY, rotZ), camDistance)
            #                 if len(newPoints) > 2:
            #                     face.points = newPoints
            #                     newSubFaces.append(face)
            #         except AttributeError:
            #             pass
            #         texturedFace.set_sub_faces(newSubFaces)
            #         allFaces.append(texturedFace)
            # faces = cube.get_faces(playerPos)
            # # fix points behind camera and remove faces with two or fewer remaining points
            # for face in faces:
            #     newPoints = fix_points_behind_camera(rotate(face.points, rotX, rotY, rotZ), camDistance)
            #     if len(newPoints) > 2:
            #         face.points = newPoints
            #         allFaces.append(face)
        # whyyyy
        # add faces for placeable cube
        if placing:
            # generate the position for the new cube ('ray cast' out of camera and do weird things)
            newCubePos = Point3D(reverse_rotate([[blockDistance, 0, 0]], rotX, rotY, -rotZ + 90)[0]) # todo change this to a .applymatrix
            newCubePos.inv_translate(playerPos)
            newCubePos.snap()

            # generate cube object with position of newCubePos
            newCube = Cube(newCubePos, placingColours[currentTexture], True, keys[pygame.K_LCTRL])
            # take faces out of cube object and never speak of it again
            for face in newCube.get_faces():
                allFaces.append(face)

            # # fix points behind camera and remove faces with two or fewer remaining points
            # for face in faces:
            #     newPoints = fix_points_behind_camera(rotate(face.points, rotX, rotY, rotZ), camDistance)
            #     if len(newPoints) > 2:
            #         face.points = newPoints
            #         allFaces.append(face)

        # ______draw all faces______
        # apply camMatrix to allFaces
        for face in allFaces:
            # newpoints: list[Point3D] = []
            for point in face.points:
                point.apply_matrix(camMatrix)
                # newpoints.append(Point3D([0, 0, 0]))
                # point = Point3D(rotate(translate([point.xyz()], playerPos.xyz()), rotX, rotY, rotZ)[0])
            # face.points = newpoints
            # fix points behind the camera
            face.fix_points_behind_camera()
            # if len(face.points) < 3:
            #    allFaces.remove(face)

        # sort the list of allFaces
        # iterates through all faces - iterates through each point axis - iterates through points within faces
        # (strange order)
        allFaces_sort = []
        for face in allFaces:
            average = Point3D([0, 0, 0])
            for index in range(3):
                for point in face.points:
                    average.xyz()[index] += point.xyz()[index]
                average.xyz()[index] = average.xyz()[index] / 3

            # 3d pythagoras to find the distance to the camera
            allFaces_sort.append(-(average.x ** 2 + average.y ** 2 + average.z ** 2))
        allFaces = [x for _, x in sorted(zip(allFaces_sort, allFaces), key=lambda item: item[0])]

        # draw allFaces
        i = 0
        for i, face in enumerate(allFaces):
            _2dPoints = [point.get_2d_point(FPDis).conv_coord(size, scale) for point in face.points]
#            _2dPoints = [point.conv_coord(size, scale) for point in _2dPoints]  # this seems really illegal

            try:
                if True:  # face_angle([face.points[0], face.points[1], face.points[2]], FPDis, camDistance) > 90:
                    if face.hasFaces and len(face.points) > 2:
                        pygame.draw.polygon(mainSurface, tuple(face.colour.get_rgb()), [point.get() for point in _2dPoints], 0)
                    if face.hasEdges:
                        for i2 in range(len(_2dPoints)):
                            pygame.draw.aaline(mainSurface, (0, 0, 0), _2dPoints[i2].get(),
                                               _2dPoints[(i2 + 1) % len(_2dPoints)].get())
            except IndexError:
                print("found face with only 2 points (This error occurring here indicates a bug in the code somewhere)")
        allFaces.clear()
        # _____end face drawing_____

        # draw axes
        # _3dPoints = rotate(translate(axesPoints, playerPos), rotX, rotY, rotZ)
        # for i in range(len(axesEdges)):
        #     if not (_3dPoints[axesEdges[i][0]][1] <= camDistance and _3dPoints[axesEdges[i][1]][1] <= camDistance):
        #         _new3dPoints = fix_points_behind_camera_edge(_3dPoints[axesEdges[i][0]], _3dPoints[axesEdges[i][1]],
        #                                                      camDistance)
        #         _2dPoints = conv_coord(project(_new3dPoints, FPDis, camDistance), size, scale)

        # player movement
        # speed increase with key press
        if keys[pygame.K_LCTRL] == 1:
            sprint = 5
        else:
            sprint = 1

        # calculate the key presses, figure out a force direction, then rotate it, then apply a magnitude,
        # then apply that to the player speed, subtract the drag (value proportional to the speed) from the vector.
        pressedKeys = [(keys[pygame.K_a] - keys[pygame.K_d]),
                       (keys[pygame.K_s] - keys[pygame.K_w]),
                       (keys[pygame.K_SPACE] - keys[pygame.K_LSHIFT])]
        try:
            magnitude = 1 / math.sqrt(pressedKeys[0] ** 2 + pressedKeys[1] ** 2 + pressedKeys[2] ** 2)
        except ZeroDivisionError:
            magnitude = 0

        vectoredDirection = [x * magnitude for x in pressedKeys]
        rotatedVector = rotate([vectoredDirection], 0, 0, -rotZ)[0]

        playerSpeed = Point3D([(speed + ((acc / frameRate) * kPress)) * (1 - (((acc-1) / maxSpeed)/frameRate))
                               for speed, kPress in zip(playerSpeed.xyz(), rotatedVector)])

        # handle speed and movement in that direction
        move = Point3D([x / frameRate for x in playerSpeed.xyz()])
        playerPos.translate(move)

        screen.blit(mainSurface, (0, 0))
        pygame.display.update()
    # END EVERY FRAME

    # handle events
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
            pygame.display.quit()
        # keyboard
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_BACKSPACE:
                running = False
            elif event.key == pygame.K_ESCAPE:
                grabbed = False
                pygame.event.set_grab(False)
                pygame.mouse.set_visible(True)
            elif event.key == pygame.K_0:
                placing = False
            elif event.key == pygame.K_1:
                placing = True
        # mouse
        elif event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == pygame.BUTTON_MIDDLE:
                pygame.mouse.get_rel()
                pygame.event.set_grab(True)
                pygame.mouse.set_visible(False)
                grabbed = True
            elif event.button == pygame.BUTTON_RIGHT:
                if placing:
                    cubes.append(Cube(newCubePos, placingColours[currentTexture], True, keys[pygame.K_LCTRL]))

            elif event.button == pygame.BUTTON_LEFT:
                if placing:
                    try:
                        for x, cube in enumerate(cubes):
                            if cube.position == newCubePos:
                                cubes.pop(x)
                    except ValueError:
                        continue

        elif event.type == pygame.MOUSEWHEEL:
            if keys[pygame.K_LCTRL]:
                currentTexture = (currentTexture + 1) % len(placingColours)
            else:
                blockDistance += event.y