diff --git a/Foxgine/Cam.py b/Foxgine/Cam.py new file mode 100644 index 0000000..679150e --- /dev/null +++ b/Foxgine/Cam.py @@ -0,0 +1,54 @@ +from math import * + +class Cam: + def __init__(self, pos=(0, 0, 0), rot=(0, 0)): + self.pos = list(pos) + self.rot = list(rot) + self.style = 0 + self.yVel = 0 + self.falling = 0 + + def update(self, dt, buttons, objects): + s = dt * 10 + + if self.style == 0: + x, y = s * sin(self.rot[1]), s * cos(self.rot[1]) + if buttons.buttonU.pressed(): + self.pos[0] += x + self.pos[2] += y + if buttons.buttonD.pressed(): + self.pos[0] -= x + self.pos[2] -= y + if buttons.buttonL.pressed(): + self.pos[0] -= y + self.pos[2] += x + if buttons.buttonR.pressed(): + self.pos[0] += y + self.pos[2] -= x + + elif self.style == 1: + if buttons.buttonR.pressed(): + self.rot[1] += 0.05 + if buttons.buttonL.pressed(): + self.rot[1] -= 0.05 + if buttons.buttonU.pressed(): + self.rot[0] += 0.05 + if buttons.buttonD.pressed(): + self.rot[0] -= 0.05 + + if buttons.buttonA.justPressed() and self.falling < 1: + print("Jump initiated!") + self.yVel = 0.8 + + self.yVel -= 0.1 + self.pos[1] += self.yVel + + if buttons.buttonB.justPressed(): + self.style = (self.style + 1) % 2 + + self.falling += 1 + + if self.pos[1] <= 1: + self.falling = 0 + self.yVel = 0 + self.pos[1] = 1 \ No newline at end of file diff --git a/Foxgine/Foxgine.py b/Foxgine/Foxgine.py new file mode 100644 index 0000000..bb336b2 --- /dev/null +++ b/Foxgine/Foxgine.py @@ -0,0 +1,109 @@ +import thumbyButton as buttons +from math import sin, cos, radians +import ujson +from sys import path as syspath +syspath.insert(0, '/Games/Foxgine') +from thumbyGrayscale import display +from Cam import Cam +from Obj3D import Object3D + +w, h = 72, 40 +cx, cy = w // 2, h // 2 +fov = 50 +dt = 1 / 30 +rendType = 0 +gameStart = 0 +rendAmt = 2 +objects = [] + +display.setFont("/lib/font3x5.bin", 3, 5, 1) +display.setFPS(30) + +cam = Cam((0, 1, -5)) +file_path = "/Games/Foxgine/models.txt" + +try: + with open(file_path, "r") as file: + fileObjs = ujson.load(file) +except Exception as e: + print(f"Error loading JSON file: {e}") + fileObjs = {} + +def makeObjs(fileObjs, rendType): + objMaker = [] + objPos = [[0, 0, 0], [0, 0, 5], [0, 1, 0]] + combinedObjs = ["quad", "tri", "test"] + + combined_verts = [] + combined_polys = [] + + vert_offset = 0 + + for i, obj_name in enumerate(combinedObjs): + verts = fileObjs[obj_name]["Verticies"] + polys = fileObjs[obj_name]["Polygons"] + pos = objPos[i] + + for vert in verts: + combined_verts.append([vert[0] + pos[0], vert[1] + pos[1], vert[2] + pos[2]]) + + for poly in polys: + adjusted_poly = [index + vert_offset if isinstance(index, int) else index for index in poly] + combined_polys.append(adjusted_poly) + + vert_offset += len(verts) + + fileObjs["combined"]["Verticies"] = combined_verts + fileObjs["combined"]["Polygons"] = combined_polys + + print(fileObjs["combined"]["Verticies"]) + print(fileObjs["combined"]["Polygons"]) + + objMaker.append(Object3D(fileObjs, "combined", (0, 0, 0), rendType)) + objMaker.append(Object3D(fileObjs, "tris", (0, 3, 0), rendType)) + + for obj_name in combinedObjs: + fileObjs[obj_name]["Verticies"].clear() + fileObjs[obj_name]["Polygons"].clear() + + objPos.clear() + combinedObjs.clear() + + return objMaker + +def distance(obj, cam): + dx = obj.pos[0] - cam.pos[0] + dy = obj.pos[1] - cam.pos[1] + dz = obj.pos[2] - cam.pos[2] + return dx * dx + dy * dy + dz * dz + +while True: + display.update() + display.fill(0) + + if gameStart == 1: + objects.sort(key=lambda obj: distance(obj, cam), reverse=True) + for obj in objects: + obj.render(fov, (w, h), (cx, cy), cam, -1, 20) + + cam.update(dt, buttons, objects) + + display.drawText("X: " + str(int(cam.pos[0])), 2, 2, 2) + display.drawText("Y: " + str(int(cam.pos[1])), 2, 8, 2) + display.drawText("Z: " + str(int(cam.pos[2])), 35, 8, 2) + display.drawText("Style: " + str(cam.style), 35, 2, 2) + elif gameStart == 0: + display.drawText("Press A for...", 10, 3, 1) + display.drawText("Filled Polys!", 10, 10, 2) + + display.drawText("Press B for...", 10, 26, 1) + display.drawText("Lined Polys!", 10, 33, 2) + + if buttons.buttonA.justPressed(): + rendType = 1 + gameStart = 1 + objects = makeObjs(fileObjs, rendType) + if buttons.buttonB.justPressed(): + rendType = 0 + gameStart = 1 + objects = makeObjs(fileObjs, rendType) \ No newline at end of file diff --git a/Foxgine/Obj3D.py b/Foxgine/Obj3D.py new file mode 100644 index 0000000..fd743a1 --- /dev/null +++ b/Foxgine/Obj3D.py @@ -0,0 +1,127 @@ +import ujson +from thumbyGrayscale import display +from math import * + +class Object3D: + def __init__(self, fileObjs, model_name, pos, rendType): + self.models = fileObjs + self.model_name = model_name + self.pos = pos + self.vert_list = [] + self.screen_coords = [] + self.rendType = rendType + self.skip = 0 + + def render(self, fov, w_h, center, cam, min_depth, max_depth): + verts = self.models[self.model_name]["Verticies"] + faces = self.models[self.model_name]["Polygons"] + + self.vert_list.clear() + self.screen_coords.clear() + + for x, y, z in verts: + x += self.pos[0] + y += self.pos[1] + z += self.pos[2] + + x, z = rotate2D((x - cam.pos[0], z - cam.pos[2]), cam.rot[1]) + y, z = rotate2D((y - cam.pos[1], z), cam.rot[0]) + + if z == 0: + z += 0.01 + + f = fov / z + x, y = x * f, y * f + self.screen_coords.append((center[0] + int(x), center[1] - int(y), z)) + self.vert_list.append((x, y, z)) + + for face in faces: + if len(face) < 4: + continue + + color = face[-2] + cull_flag = face[-1] + + face1 = face[:3] + face2 = [face[0], face[2], face[3]] + + def process_face(face_part): + vertices = [self.screen_coords[i] for i in face_part] + + if cull_flag == 1: + v0 = self.vert_list[face_part[0]] + v1 = self.vert_list[face_part[1]] + v2 = self.vert_list[face_part[2]] + normal = compute_normal(v0, v1, v2) + if normal[2] >= 0: + return + + on_screen = all(min_depth < self.vert_list[i][2] < max_depth and + -150 <= vertices[i][0] < (w_h[0] + 100) and + -150 <= vertices[i][1] < (w_h[1] + 100) for i in range(3)) + + if on_screen: + if self.rendType == 0: + self.draw_edges(vertices, color) + if self.rendType == 1: + self.fill_polygon(vertices, color) + + process_face(face1) + if len(face) > 5: + process_face(face2) + + def draw_edges(self, vertices, color): + for i in range(len(vertices)): + x0, y0, _ = vertices[i] + x1, y1, _ = vertices[(i + 1) % len(vertices)] + self.draw_line(x0, y0, x1, y1, color) + + def fill_polygon(self, vertices, color): + vertices = sorted(vertices, key=lambda v: v[1]) + + if len(vertices) < 3: + return + + min_y = max(int(vertices[0][1]), 0) + max_y = min(int(vertices[-1][1]), display.height - 1) + + for y in range(min_y, max_y, 2): + intersections = [] + + for i in range(len(vertices)): + x1, y1, _ = vertices[i] + x2, y2, _ = vertices[(i + 1) % len(vertices)] + + if y1 <= y < y2 or y2 <= y < y1: + if y2 != y1: + x_inter = x1 + (y - y1) * (x2 - x1) / (y2 - y1) + intersections.append(x_inter) + + if len(intersections) < 2: + continue + + intersections.sort() + for j in range(0, len(intersections), 2): + x_start = max(int(intersections[j]), 0) + x_end = min(int(intersections[j + 1]), display.width - 1) + for x in range(x_start, x_end, 1): + display.setPixel(x, y, color) + + def draw_line(self, x0, y0, x1, y1, color): + display.drawLine(int(x0), int(y0), int(x1), int(y1), color) + + def distance_from_player(self, player_pos): + dx = self.pos[0] - player_pos[0] + dz = self.pos[2] - player_pos[2] + return sqrt(dx * dx + dz * dz) + +def rotate2D(pos, rad): + x, y = pos + s, c = sin(rad), cos(rad) + return x * c - y * s, y * c + x * s + +def compute_normal(v0, v1, v2): + u = (v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]) + v = (v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]) + normal = (u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2], u[0] * v[1] - u[1] * v[0]) + return normal diff --git a/Foxgine/arcade_description.txt.txt b/Foxgine/arcade_description.txt.txt new file mode 100644 index 0000000..84e00b4 --- /dev/null +++ b/Foxgine/arcade_description.txt.txt @@ -0,0 +1,7 @@ +A simple 3D engine that was made to be able to render out files similar to an obj + +Author: PresentFox +Version: 1.0 +License: Open Source ( please tag me if you use this engine! ) + +Thank you: Kada, Openmindedness, Demod ( for giving me advice and tips on 3D stuff! ) \ No newline at end of file diff --git a/Foxgine/arcade_title_video.webm.webm b/Foxgine/arcade_title_video.webm.webm new file mode 100644 index 0000000..a8615d7 Binary files /dev/null and b/Foxgine/arcade_title_video.webm.webm differ diff --git a/Foxgine/models.txt b/Foxgine/models.txt new file mode 100644 index 0000000..0e44397 --- /dev/null +++ b/Foxgine/models.txt @@ -0,0 +1,51 @@ +{ + "quad": { + "Verticies": [ + [0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], + [0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1] + ], + "Polygons": [ + [3, 2, 1, 0, 1, 1], [4, 5, 6, 7, 3, 1], [0, 1, 5, 4, 1, 1], + [1, 2, 6, 5, 1, 1], [2, 3, 7, 6, 2, 1], [3, 0, 4, 7, 3, 1] + ] + } + + "tri": { + "Verticies": [ + [0, 0, 0], [1, 0, 0], [1, 1, 0], + [0, 1, 0], [0, 0, 1], [1, 0, 1] + ], + "Polygons": [ + [0, 1, 2, 1, 0], + [3, 4, 5, 2, 0] + ] + } + + "tris": { + "Verticies": [ + [0, 0, 0], [1, 0, 0], [1, 1, 0], + [0, 1, 0], [0, 0, 1], [1, 0, 1] + ], + "Polygons": [ + [0, 1, 2, 1, 0], + [3, 4, 5, 2, 0] + ] + } + + "test": { + "Verticies": [ + [0, 0, 0], [1, 0, 0], [1, 1, 0], + [0, 1, 0], [0, 0, 1], [1, 0, 1] + ], + "Polygons": [ + [0, 1, 2, 3, 1, 0], + [3, 4, 5, 1, 3, 0], + [3, 4, 0, 2, 0] + ] + } + + "combined": { + "Verticies": [], + "Polygons": [] + } +} \ No newline at end of file