Newer
Older
3d_renderer / main.py
@cory cory on 19 Mar 2022 12 KB initial commit
# TODO
# make todo list

# import
import copy

import pygame
from functions import *

# variables
# screen size in pixels
size = (1000, 1000)
# screen with in units
scrWidth = 3.2
playerPos = [0, 10, 0]
playerSpeed = [0, 0.01, 0]

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 = (0, 0, 0)
fgColour = (255, 255, 255)
polyColour = [(255, 120, 120), (255, 120, 120), (255, 120, 120), (255, 120, 120), (255, 120, 120), (255, 120, 120)]
hue = 0

# 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])]]

print(placingColours)
currentTexture: int = 0

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(x, Colour([255, 0, 0]), True, True))


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

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)

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

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

# create surfaces
mainSurface = pygame.Surface(size)

grass = [Texture("grass.png") for x in range(6)]
cubes.append(Cube([0, 2, 3], grass, True, True))

for i in range(len(polyColour)):
    polyColour[i] = tuple(255 * x for x in colorsys.hsv_to_rgb(hue, .5, 1))
    hue += 1 / 6

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)

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

        # __draw cubes__
        # calculate 3d and project 3d point locations
        col = 0
        for cube in cubes:
            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)

        # add faces for placeable cube
        if placing:
            # generate the position for the new cube ('ray cast' out of camera and do weird things)
            newCubePos = reverse_rotate([[blockDistance, 0, 0]], rotX, rotY, -rotZ + 90)
            newCubePos = translate(newCubePos, [-x for x in playerPos])
            newCubePos = [[int(round(x)) for x in newCubePos[0]]]
            # generate cube object with position of newCubePos
            newCube = Cube(newCubePos[0], placingColours[currentTexture], True, keys[pygame.K_LCTRL])
            # take faces out of cube object and never speak of it again
            faces = newCube.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)
        # ______ TESTING ____
        # generate the points for a subdivided 16*16 face
        grass = Texture("grass.png")

        sub_div_points = []
        y = -.5
        for i in range(17):
            x = -.5
            sub_div_points.append([])
            for j in range(17):
                sub_div_points[i].append([x, y])
                x += 1 / 16
            y += 1 / 16

        # generate the face_list for a subdivided 16*16 face
        sub_div_faces = []
        y = 0
        for i in range(16):
            x = 0
            for j in range(16):
                sub_div_faces.append([[x, y], [1 + x, y], [1 + x, 1 + y], [x, y + 1]])
                x += 1
            y += 1

        for i, face in enumerate(sub_div_faces):
            points = [copy.deepcopy(sub_div_points[point[0]][point[1]]) for point in sub_div_faces[i]]
            for point in points:
                point.insert(2, 1)
            y = i % 16
            x = i // 16
            newPoints = fix_points_behind_camera(rotate(translate(points, playerPos), rotX, rotY, rotZ), camDistance)
            if len(newPoints) > 2:
                allFaces.append(Face(newPoints, grass.get_pixel(x, y), False, True))

        # ______draw all faces______
        # 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 = [0, 0, 0]
            for index in range(3):
                for point in face.points:
                    average[index] += point[index]
                average[index] = average[index] / 3

            # 3d pythagoras to find the distance to the camera
            allFaces_sort.append(-(average[0] ** 2 + average[1] ** 2 + average[2] ** 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 = conv_coord(project(face.points, FPDis, camDistance), size, scale)
            try:
                if True: # face_angle([face.points[0], face.points[1], face.points[2]], FPDis, camDistance) > 90:
                    if face.hasFaces:
                        pygame.draw.polygon(mainSurface, tuple(face.colour.get_rgb()), _2dPoints, 0)
                    if face.hasEdges:
                        for i2 in range(len(_2dPoints)):
                            pygame.draw.aaline(mainSurface, (0, 0, 0), _2dPoints[i2],
                                               _2dPoints[(i2 + 1) % len(_2dPoints)])
            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 = [(speed + ((acc / frameRate) * kPress)) * (1 - (((acc-1) / maxSpeed)/frameRate))
                       for speed, kPress in zip(playerSpeed, rotatedVector)]

        # handle speed and movement in that direction
        move = [x / frameRate for x in playerSpeed]
        for j in range(3):
            playerPos[j] += move[j]

        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[0], 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[0]:
                                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