diff --git a/Pipfile b/Pipfile index c0fb560..1dadb7e 100644 --- a/Pipfile +++ b/Pipfile @@ -6,6 +6,7 @@ [packages] pygame = "*" imageio = "*" +pyopengl = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index f812ece..bec4357 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "a303338fcd44cb50c8ab87df55b29b09f929cf9a2f33231cf83524a8a3742fd0" + "sha256": "c123dc2ee3eb0567712759689afdebd7450557aaf7ad9cc6f843cbb02be1d804" }, "pipfile-spec": 6, "requires": { @@ -154,6 +154,15 @@ ], "index": "pypi", "version": "==2.1.2" + }, + "pyopengl": { + "hashes": [ + "sha256:57c597d989178e1413002df6b923619f6d29807501dece1c60cc6f12c0c8e8a7", + "sha256:8ea6c8773927eda7405bffc6f5bb93be81569a7b05c8cac50cd94e969dce5e27", + "sha256:a7139bc3e15d656feae1f7e3ef68c799941ed43fadc78177a23db7e946c20738" + ], + "index": "pypi", + "version": "==3.1.6" } }, "develop": {} diff --git a/functions.py b/functions.py index 79e6f01..ee7029c 100644 --- a/functions.py +++ b/functions.py @@ -142,7 +142,6 @@ for i in range(len(points3d)): if points3d[i][1] <= distance + .5: invalid_points.append(points3d[i]) - # print(points3d[i][1], distance) for j in range(len(face_list)): try: face_list[j].remove(i) @@ -201,13 +200,19 @@ [0, 0, 1, player_pos.z], [0, 0, 0, 1]] ) + # todo implement mapping matrix + # map_matrix = numpy.array([[1, 0, 0 / fp_dis, 0], + # [0, 1, 0 / fp_dis, 0], + # [0, 0, 1 / fp_dis, 0], + # [0, 0, 0, 1]]) + return rotation_matrix.dot(translation_matrix) def gen_inv_rot_matrix(rx_d, ry_d, rz_d): - rx = math.radians(-rx_d) - ry = math.radians(rx_d) - rz = math.radians(-rz_d - 90) + rx = math.radians(rx_d) + ry = math.radians(- rx_d + 90) + rz = math.radians(- rz_d + 90) x_rot = numpy.array([[1, 0, 0, 0], [0, math.cos(rx), math.sin(rx), 0], diff --git a/main.py b/main.py index 0503e6c..5cbe72d 100644 --- a/main.py +++ b/main.py @@ -3,9 +3,10 @@ # import import colorsys - +import copy import pygame from functions import * +import multiprocessing as mp # variables # screen size in pixels @@ -13,7 +14,7 @@ size = (1000, 1000) # screen with in units -scrWidth = 3.2 +scrWidth = 1 # player information playerPos: Point3D = Point3D(0, 0, 0) @@ -42,9 +43,13 @@ 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])]] +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 @@ -66,20 +71,22 @@ # rendering variables # rotation +rotX = 90 rotY = 0 -rotX = 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) -FPDis = fov_calc(camFOV, scrWidth) - +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 @@ -90,7 +97,7 @@ # initial values for variables so pycharm doesn't get mad keys = pygame.key.get_pressed() -newCubePos: Point3D = Point3D(0, 0, 0) +newCube = Cube(Point3D(0, 0, 0), placingColours[0], False, False) # settings pygame.mouse.set_visible(True) @@ -102,22 +109,21 @@ while running: # every tick: - if lastMillis[1] + 16.6 <= pygame.time.get_ticks() or triggerDraw: + 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 + 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 rotX > 180: + rotX = 180 + elif rotX < 0: + rotX = 0 if not triggerDraw: lastMillis[1] = pygame.time.get_ticks() @@ -161,37 +167,56 @@ # 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._make([reverse_rotate([[blockDistance, 0, 0]], rotX, rotY, -rotZ + 90)[0]][0]) + # 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) - # todo change this to a .applymatrix newCubePos = newCubePos.inv_translate(playerPos) newCubePos = 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 + # 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) - # # 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) + # 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 - # ______draw all faces______ - # apply camMatrix to allFaces 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() + 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 @@ -211,29 +236,21 @@ # 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.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) + 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)") + pass 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: @@ -244,7 +261,7 @@ # 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_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) @@ -262,6 +279,9 @@ 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 @@ -283,6 +303,7 @@ placing = False elif event.key == pygame.K_1: placing = True + # mouse elif event.type == pygame.MOUSEBUTTONDOWN: if event.button == pygame.BUTTON_MIDDLE: @@ -292,13 +313,21 @@ grabbed = True elif event.button == pygame.BUTTON_RIGHT: if placing: - cubes.append(Cube(newCubePos, placingColours[currentTexture], True, keys[pygame.K_LCTRL])) + # 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 == newCubePos: + if cube.position == newCube.position: cubes.pop(x) except ValueError: continue diff --git a/model.py b/model.py index 68118ff..95231d8 100644 --- a/model.py +++ b/model.py @@ -56,7 +56,7 @@ return [self.x, self.y] def conv_coord(self, size, scale): - return Point2D((self.x * scale) + size[0]/2, (self.y * scale) + size[1]/2) + return Point2D((self.x * scale) + size[0] / 2, (self.y * scale) + size[1] / 2) class AbstractPoint3D(NamedTuple): @@ -83,7 +83,7 @@ def inv_translate(self, t_vec): return Point3D(self.x - t_vec.x, self.y - t_vec.y, - self.z - t_vec.z,) + self.z - t_vec.z, ) def snap(self): # sets x y z to the nearest integer return Point3D(round(self.x), round(self.y), round(self.z)) @@ -94,18 +94,21 @@ point = numpy.delete(point, 3) return Point3D._make(point) - def get_2d_point(self, fp_distance): + def get_2d_point(self, map_matrix): # converts a 3d point into a 2d point (replacement for the projection function) - distance = 0 - # calculate x pos - try: - x = ((fp_distance * self.x) / (self.y - distance)) - # calculate y pos - y = ((fp_distance * self.z) / (self.y - distance)) - except ZeroDivisionError: - x = 0 - y = 0 - return Point2D(x, y) + # distance = 0 + pos = map_matrix.dot(numpy.array([self.x, self.y, self.z])) + # convert from homogenous coordinate to cartesian coordinate + pos = [pos[0] / pos[2], pos[1] / pos[2]] + # try: + # x = ((fp_distance * self.x) / (self.y - distance)) + # # calculate y pos + # y = ((fp_distance * self.z) / (self.y - distance)) + # except ZeroDivisionError: + # x = 0 + # y = 0 + + return Point2D(pos[0], pos[1]) def to_list(self): return [self.x, self.y, self.z] @@ -157,12 +160,38 @@ # returns the positions of points (not rotated, not relative to the player) return self.points - def fix_points_behind_camera(self): - new_points: list[Point3D] = [] + def fix_points_behind_camera(self, fp_dis): + cut_off = fp_dis + .05 + new_points: list[list[Point3D, bool]] = [] + invalid_points = 0 + # find and mark invalid points + for point in self.points: - if not(point.y <= .5): - new_points.append(point) - self.points = new_points + if point.z <= cut_off: + new_points.append([point, False]) + invalid_points += 1 + else: + new_points.append([point, True]) + if invalid_points == len(self.points): # check if there are no remaining points + self.points = [] + return + valid_points = [] + for j, point in enumerate(new_points): + if point[1]: + valid_points.append(point[0]) + else: + # check points forward and backwards in list + # could just copy and paste the code twice instead of a for loop but it felt wrong. + for k in [-1, 1]: + if new_points[(j + k) % len(new_points)][1]: + front_point = new_points[(j + k) % len(new_points)][0] + diff = front_point.inv_translate(point[0]) + percentage = (front_point.z - cut_off) / (front_point.z - point[0].z) + + new_back_point = Point3D(front_point.x - (diff.x * percentage), + front_point.y - (diff.y * percentage), cut_off) + valid_points.append(new_back_point) + self.points = valid_points class TexturedFace(Face): @@ -171,6 +200,7 @@ # figure out a way of storing the 'index' of the face (probably in the cube class) texture: Texture + # This class inherits from Face, and instead of a base colour it has a texture def set_texture(self, texture: Texture): @@ -202,9 +232,9 @@ [cubePoints[4], cubePoints[5], cubePoints[7], cubePoints[6]], # east [cubePoints[0], cubePoints[1], cubePoints[5], cubePoints[4]], # south [cubePoints[0], cubePoints[2], cubePoints[3], cubePoints[1]], # west - [cubePoints[6], cubePoints[2], cubePoints[0], cubePoints[4]]] # bottom] + [cubePoints[6], cubePoints[2], cubePoints[0], cubePoints[4]]] # bottom] - def __init__(self, position: Point3D, texture : list[Colour], has_faces, has_edges): + def __init__(self, position: Point3D, texture: list[Colour], has_faces, has_edges): self.faces = [] self.position = Point3D(0, 0, 0) # todo @@ -215,7 +245,7 @@ self.has_faces = has_faces self.has_edges = has_edges - # initialises faces when the cube is placed. Faces are in global coordinates + # This initialises faces when the cube is placed. Faces are in global coordinates for i in range(6): new_points = 0 self.faces.append(Face([point.translate(self.position) for point in copy.deepcopy(self.cubeFaces[i])], @@ -223,3 +253,11 @@ def get_faces(self): return copy.deepcopy(self.faces) + + def set_faces(self, visible): + self.has_faces = visible + return self + + def set_edges(self, visible): + self.has_edges = visible + return self diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f7bb762 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pygame~=2.1.2 +imageio~=2.16.1 +numpy~=1.22.3 \ No newline at end of file