From 05d2beb8acf6c3150ef74ce2c2fc70a54bb5fc66 Mon Sep 17 00:00:00 2001 From: Garritt McCune Date: Tue, 14 Jul 2020 16:53:06 -0500 Subject: [PATCH] Implemented the first version of the 3D engine. Added support for loading 'obj' files as a mesh. --- engine.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ main.py | 52 +++++++++++++--------------------------------------- matrix.py | 11 +---------- vectors.py | 15 +++++++++++++++ 4 files changed, 75 insertions(+), 49 deletions(-) create mode 100644 engine.py diff --git a/engine.py b/engine.py new file mode 100644 index 0000000..5e320ac --- /dev/null +++ b/engine.py @@ -0,0 +1,46 @@ +from vectors import Vector2d, Vector3d, Vector4d +from matrix import Matrix3x3 + +class Engine: + def __init__(self, width, height): + self._width = width + self._height = height + + @property + def width(self): + return self._width + + @width.setter + def width(self, width): + raise ValueError("Not allowed") + +class Mesh: + def __init__(self): + self.triangles = [] + self.vertices = [] + + def load_obj_file(self, path): + lines = [] + + with open(path, 'r') as reader: + lines = reader.readlines() + + for line in lines: + if line[0] == 'v': + tmp = line.split(' ') + x = float(tmp[1]) + y = float(tmp[2]) + z = float(tmp[3]) + self.vertices.append(Vector3d(x, y, z)) + + if line[0] == 'f': + tmp = line.split(' ') + + # Subtract one because the obj file isn't zero indexed. + row0 = self.vertices[int(tmp[1]) - 1] + row1 = self.vertices[int(tmp[2]) - 1] + row2 = self.vertices[int(tmp[3]) - 1] + + tri = Matrix3x3(row0, row1, row2) + + self.triangles.append(tri)#Vector3d(x - 1, y - 1, z - 1)) diff --git a/main.py b/main.py index 4b8f431..4e7f813 100644 --- a/main.py +++ b/main.py @@ -3,44 +3,23 @@ import math import copy from matrix import Matrix4x4, Matrix3x3 from vectors import Vector4d, Vector3d, Vector2d +from engine import Mesh ui = window.Window(600, 600) near_plane = 0.1 far_plane = 1000.0 fov = 90.0 -tris = [ - # SOUTH - [ [0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0] ], - [ [0.0, 0.0, 0.0], [1.0, 1.0, 0.0], [1.0, 0.0, 0.0 ]], - - # EAST - [ [1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [1.0, 1.0, 1.0 ]], - [ [1.0, 0.0, 0.0], [1.0, 1.0, 1.0], [1.0, 0.0, 1.0 ]], - - # NORTH - [ [1.0, 0.0, 1.0], [1.0, 1.0, 1.0], [0.0, 1.0, 1.0 ]], - [ [1.0, 0.0, 1.0], [0.0, 1.0, 1.0], [0.0, 0.0, 1.0 ]], - - # WEST - [[ 0.0, 0.0, 1.0], [0.0, 1.0, 1.0], [0.0, 1.0, 0.0 ]], - [ [0.0, 0.0, 1.0], [0.0, 1.0, 0.0], [0.0, 0.0, 0.0 ]], - - # TOP - [[ 0.0, 1.0, 0.0], [0.0, 1.0, 1.0], [1.0, 1.0, 1.0 ]], - [[ 0.0, 1.0, 0.0], [1.0, 1.0, 1.0], [1.0, 1.0, 0.0 ]], - - # BOTTOM - [[ 1.0, 0.0, 1.0], [0.0, 0.0, 1.0], [0.0, 0.0, 0.0 ]], - [[ 1.0, 0.0, 1.0], [0.0, 0.0, 0.0], [1.0, 0.0, 0.0 ]] -] +tris = Mesh() def main(): + tris.load_obj_file('shuttle.obj') + ui.thetax = 1 ui.window.bind('', left) ui.window.bind('', right) ui.window.bind('', mouse_start) - ui.window.bind('', mouse_end) + ui.canvas.bind('', mouse_end) ui.canvas.old_coords = None ui.window.after(0, draw_scene) ui.show() @@ -97,30 +76,25 @@ def draw_scene(): rotation_matrix_x = Matrix4x4(row0, row1, row2, row3) - for t in tris: - row0 = Vector3d(t[0][0], t[0][1], t[0][2]) - row1 = Vector3d(t[1][0], t[1][1], t[1][2]) - row2 = Vector3d(t[2][0], t[2][1], t[2][2]) - - tri = Matrix3x3(row0, row1, row2) - + for tri in tris.triangles: + # Rotate on the z-axis row0 = rotation_matrix_z.multiply_3d(tri.row0) row1 = rotation_matrix_z.multiply_3d(tri.row1) row2 = rotation_matrix_z.multiply_3d(tri.row2) rotated_z = Matrix3x3(row0, row1, row2) - + # Rotate on the x-axis row0 = rotation_matrix_x.multiply_3d(rotated_z.row0) row1 = rotation_matrix_x.multiply_3d(rotated_z.row1) row2 = rotation_matrix_x.multiply_3d(rotated_z.row2) rotated_x = Matrix3x3(row0, row1, row2) - + # The offset into the screen translated = copy.copy(rotated_x) - translated.row0.z += 3.0 - translated.row1.z += 3.0 - translated.row2.z += 3.0 - + translated.row0.z += 8.0 + translated.row1.z += 8.0 + translated.row2.z += 8.0 + # Scale into view row0 = projection_matrix.multiply_3d(translated.row0) row1 = projection_matrix.multiply_3d(translated.row1) row2 = projection_matrix.multiply_3d(translated.row2) diff --git a/matrix.py b/matrix.py index 2d07717..b5cf884 100644 --- a/matrix.py +++ b/matrix.py @@ -1,12 +1,7 @@ import vectors class Matrix4x4: - def __init__(self): - self.row0 = None - self.row1 = None - self.row2 = None - self.row3 = None - + def __init__(self, row0, row1, row2, row3): if not isinstance(row0, vectors.Vector4d): raise ValueError("Row must be a Vector4d object.", row0) @@ -36,10 +31,6 @@ class Matrix4x4: return vectors.Vector3d(x, y, z) class Matrix3x3: - def __init__(self): - self.row0 = None - self.row1 = None - self.row2 = None def __init__(self, row0, row1, row2): self.row0 = row0 diff --git a/vectors.py b/vectors.py index 7fdeba6..6746239 100644 --- a/vectors.py +++ b/vectors.py @@ -11,6 +11,21 @@ class Vector3d: self.y = y self.z = z + def cross_product(self, vector3d): + # Returns a line that is perpendicular to the parameter + # and self. + # Returns the 'normal'. + x = self.y * vector3d.z - self.z * vector3d.y + y = self.z * vector3d.x - self.x * vector3d.z + z = self.x * vectored.y - self.y * vector3d.x + + return Vector3d(x, y, z) + + def dot_product(self, vector3d): + # Returns a scalar that defines how similar two + # vectors are to one another. + return self.x * vector3d.x + self.y * vector3d.y + self.z * vector3d.z + class Vector2d: def __init__(self, x, y): self.x = x