# TODO
# make todo list
# import
import colorsys
import copy
import pygame
from functions import *
import multiprocessing as mp
# variables
# screen size in pixels
from model import Colour, Point3D, Face, Cube
size = (1000, 1000)
# screen with in units
scrWidth = 1
# player information
playerPos: Point3D = Point3D(0, 0, 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[0], x[1], x[2]), [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
rotX = 90
rotY = 0
rotZ = 0
# distance
camDistance = 0
# fov
camFOV = 120
FPDis = fov_calc(camFOV, scrWidth)
print(FPDis)
# matrices
camMatrix: numpy.array = generate_cam_matrix(rotX, rotY, rotZ, playerPos)
map_matrix = numpy.array([[1, 0, 0 / FPDis],
[0, 1, 0 / FPDis],
[0, 0, 1 / FPDis]])
# 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()
newCube = Cube(Point3D(0, 0, 0), placingColours[0], False, False)
# 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 * 2) <= 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 > 180:
rotX = 180
elif rotX < 0:
rotX = 0
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)
invRotMatrix = gen_inv_rot_matrix(rotX, rotY, rotZ)
# __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)
# add faces for placeable cube
if placing:
# todo fix invRotMatrix and add translation
# todo figure out why the snapping seems to apply to individual points instead of the entire cube
# generate the position for the new cube ('ray cast' out of the camera, rotate the vector and then snap
# that position to the nearest integer.
newCubePos = Point3D(blockDistance, 0, 0).apply_matrix(invRotMatrix)
newCubePos = newCubePos.inv_translate(playerPos)
newCubePos = newCubePos.snap()
# generate new cube object with position of newCubePos
newCube = Cube(newCubePos, placingColours[currentTexture], keys[pygame.K_LCTRL], True)
# take faces out of cube object and never speak of it again (woo efficiency!!)
for face in newCube.get_faces():
allFaces.append(face)
# attempt at multiprocessing (failed)
# def apply_cam_matrix(values): #allFaces, start, step):
# new_faces = []
# all_faces = values[0]
# start = values[1]
# step = values[2]
# # todo fix this
# for face_i in range(start, len(all_faces), step):
# new_face = all_faces[face_i]
# new_points = []
# for point_i in range(len(all_faces[face_i].points)):
# # apply the camera matrix to the points
# new_points.append(all_faces[face_i].points[point_i].apply_matrix(camMatrix))
# # fix points behind the camera
# new_face.points = new_points
# new_face.fix_points_behind_camera()
# if len(new_face.points) > 2:
# new_faces.append(new_face)
# return new_faces
#
# # ______draw all faces______
# # apply camMatrix to allFaces
# with mp.Pool(8) as p:
# newFacesList = p.map(apply_cam_matrix, [(allFaces, x, 8) for x in range(8)])
# allFaces = []
# for faces in newFacesList:
# allFaces = allFaces + faces
for face in allFaces:
for i, point in enumerate(face.points):
# apply the camera matrix to the points
face.points[i] = face.points[i].apply_matrix(camMatrix)
# fix points behind the camera
face.fix_points_behind_camera(FPDis)
# 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.xyz()[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 = [point.get_2d_point(map_matrix).conv_coord(size, scale) for point in face.points]
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:
pass
allFaces.clear()
# _____end face drawing_____
# 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_w] - keys[pygame.K_s]),
(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)
playerSpeed = Point3D._make(
[(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._make([x / frameRate for x in playerSpeed.xyz()])
playerPos = playerPos.translate(move)
# handle Q and E rotation
#rotY += keys[pygame.K_q] - keys[pygame.K_e]
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:
# check if there is an existing cube in the location. If not,
# append the new cube and set faces and edges to visible.
occupied = False
for x, cube in enumerate(cubes):
if cube.position == newCube.position:
occupied = True
if not occupied:
cubes.append(copy.deepcopy(newCube).set_faces(True))
cubes.append(Cube(newCube.position, newCube.colours, True, True))
elif event.button == pygame.BUTTON_LEFT:
if placing:
try:
for x, cube in enumerate(cubes):
if cube.position == newCube.position:
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