diff --git a/engine.py b/engine.py index 8542a54..5a22538 100644 --- a/engine.py +++ b/engine.py @@ -38,8 +38,8 @@ class Mesh: 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] + row1 = self.vertices[int(tmp[1]) - 1] + row2 = self.vertices[int(tmp[2]) - 1] + row3 = self.vertices[int(tmp[3]) - 1] - self.triangles.append(Triangle(Matrix3x3(row0, row1, row2))) + self.triangles.append(Triangle(Matrix3x3(row1, row2, row3))) diff --git a/main.py b/main.py index d156dd3..0ad5c66 100644 --- a/main.py +++ b/main.py @@ -10,7 +10,7 @@ ui = window.Window(600, 600) near_plane = 0.1 far_plane = 1000.0 fov = 90.0 - +#''' tris =[ Matrix3x3(Vector3d(0.0,0.0,0.0),Vector3d(0.0,1.0,0.0),Vector3d(1.0,1.0,0.0)), Matrix3x3(Vector3d(0.0,0.0,0.0),Vector3d(1.0,1.0,0.0),Vector3d(1.0,0.0,0.0)), @@ -25,14 +25,16 @@ Matrix3x3(Vector3d(0.0,1.0,0.0),Vector3d(1.0,1.0,1.0),Vector3d(1.0,1.0,0.0)), Matrix3x3(Vector3d(1.0,0.0,1.0),Vector3d(0.0,0.0,1.0),Vector3d(0.0,0.0,0.0)), Matrix3x3(Vector3d(1.0,0.0,1.0),Vector3d(0.0,0.0,0.0),Vector3d(1.0,0.0,0.0)) ] - +#''' #tris = Mesh() def main(): - #tris.load_obj_file('shuttle.obj') - + #tris.load_obj_file('teddy.obj') ui.thetax = 1 + ui.thetay = 1 ui.window.bind('', left) ui.window.bind('', right) + ui.window.bind('', up) + ui.window.bind('', down) ui.window.bind('', mouse_start) ui.canvas.bind('', mouse_end) ui.canvas.old_coords = None @@ -40,27 +42,35 @@ def main(): ui.show() def left(event): - ui.theta -= .01 + ui.thetax -= .1 + draw_scene() def right(event): - ui.theta += .01 + ui.thetax += .1 + draw_scene() + +def up(event): + ui.thetay += .1 + draw_scene() + +def down(event): + ui.thetay -= .1 + draw_scene() def mouse_start(event): ui.canvas.old_coords = event.x, event.y def mouse_end(event): - x, y = ui.canvas.old_coords[0] - event.x, ui.canvas.old_coords[1] - event.y + x, y = event.x - ui.width / 2, event.y - ui.height / 2#ui.canvas.old_coords[0] - event.x, ui.canvas.old_coords[1] - event.y if x < 0: - ui.theta += .01 + ui.thetax -= .01 else: - ui.theta -= .01 - - print(ui.theta) + ui.thetax += .01 if y < 0: - ui.thetax += .01 + ui.thetay -= .01 else: - ui.thetax -= .01 + ui.thetay += .01 draw_scene() #ui.theta += x / ui.width @@ -69,109 +79,97 @@ def draw_scene(): ui.canvas.delete("all") #ui.theta += .01 # scaling factor aspect_ratio = ui.height / ui.width - fov_rad = ui.theta / math.tan(fov * 0.5 / 180.0 * math.pi) + + projection_matrix = Matrix4x4.get_projection_matrix(fov, aspect_ratio, near_plane, far_plane) - row0 = Vector4d(aspect_ratio * fov_rad, 0, 0, 0) - row1 = Vector4d(0, fov_rad, 0, 0) - row2 = Vector4d(0, 0, far_plane / (far_plane - near_plane), 1.0) - row3 = Vector4d(0, 0, -far_plane * near_plane / (far_plane - near_plane), 0) + rotation_matrix_y = Matrix4x4.get_y_rotation_matrix(ui.thetax) - projection_matrix = Matrix4x4(row0, row1, row2, row3) - - row0 = Vector4d(math.cos(ui.theta), math.sin(ui.theta), 0, 0) - row1 = Vector4d(-math.sin(ui.theta), math.cos(ui.theta), 0, 0) - row2 = Vector4d(0, 0, 1, 0) - row3 = Vector4d(0, 0, 0, 1) - - rotation_matrix_z = Matrix4x4(row0, row1, row2, row3) - - row0 = Vector4d(1, 0, 0, 0) - row1 = Vector4d(0, math.cos(ui.thetax * 0.5), math.sin(ui.thetax * 0.5), 0) - row2 = Vector4d(0, -math.sin(ui.thetax * 0.5), math.cos(ui.thetax * 0.5), 0) - row3 = Vector4d(0, 0, 0, 1) - - rotation_matrix_x = Matrix4x4(row0, row1, row2, row3) + rotation_matrix_x = Matrix4x4.get_x_rotation_matrix(ui.thetay) triangles = [] for tri in tris: # 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) + row1 = rotation_matrix_y.multiply_3d(tri.row1) + row2 = rotation_matrix_y.multiply_3d(tri.row2) + row3 = rotation_matrix_y.multiply_3d(tri.row3) - rotated_z = Matrix3x3(row0, row1, row2) + rotated_y = Matrix3x3(row1, row2, row3) # 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) + row1 = rotation_matrix_x.multiply_3d(rotated_y.row1) + row2 = rotation_matrix_x.multiply_3d(rotated_y.row2) + row3 = rotation_matrix_x.multiply_3d(rotated_y.row3) - rotated_x = Matrix3x3(row0, row1, row2) + rotated_x = Matrix3x3(row1, row2, row3) # 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.row3.z += 3.0 # Use cross-product to get the surface normal (a Vector3d) - x = translated.row1.x - translated.row0.x - y = translated.row1.y - translated.row0.y - z = translated.row1.z - translated.row0.z + x = translated.row2.x - translated.row1.x + y = translated.row2.y - translated.row1.y + z = translated.row2.z - translated.row1.z line1 = Vector3d(x, y, z) - x = translated.row2.x - translated.row0.x - y = translated.row2.y - translated.row0.y - z = translated.row2.z - translated.row0.z + x = translated.row3.x - translated.row1.x + y = translated.row3.y - translated.row1.y + z = translated.row3.z - translated.row1.z line2 = Vector3d(x, y, z) normal = line1.cross_product(line2) # Now we normalize the normal - w = math.sqrt(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z) - normal.x /= w - normal.y /= w - normal.z /= w + length = math.sqrt(normal.x * normal.x + normal.y * normal.y + normal.z * normal.z) + normal.x /= length + normal.y /= length + normal.z /= length # Do we display this triangle? - if normal.dot_product(translated.row0) < 0.0: + if normal.dot_product(translated.row1) < 0.0: # Lighting light = Vector3d(0, 0, -1) # Shining at the player. - w = math.sqrt(light.x * light.x + light.y * light.y + light.z * light.z) - light.x /= w - light.y /= w - light.z /= w + length = math.sqrt(light.x * light.x + light.y * light.y + light.z * light.z) + light.x /= length + light.y /= length + light.z /= length dot_product = normal.dot_product(light) r = abs(int(255 * dot_product)) # Scale into view - row0 = projection_matrix.multiply_3d(translated.row0) row1 = projection_matrix.multiply_3d(translated.row1) - row2 = projection_matrix.multiply_3d(translated.row2) + row2 = projection_matrix.multiply_3d(translated.row2) + row3 = projection_matrix.multiply_3d(translated.row3) - projected = Matrix3x3(row0, row1, row2) + projected = Matrix3x3(row1, row2, row3) - projected.row0.x += 1 - projected.row0.y += 1 projected.row1.x += 1 projected.row1.y += 1 projected.row2.x += 1 projected.row2.y += 1 - projected.row0.x *= .5 * ui.width - projected.row0.y *= .5 * ui.height + projected.row3.x += 1 + projected.row3.y += 1 projected.row1.x *= .5 * ui.width projected.row1.y *= .5 * ui.height - projected.row2.x *= .5 * ui.width + projected.row2.x *= .5 * ui.width projected.row2.y *= .5 * ui.height + projected.row3.x *= .5 * ui.width + projected.row3.y *= .5 * ui.height t = Triangle(projected) t.color = r + t.average = (t.coords.row1.z + t.coords.row2.z + t.coords.row3.z) / 3 triangles.append(t) + # Sort the triangles so they are drawn in order. + triangles.sort(key=lambda r: r.average, reverse = True) + for projected in triangles: r = projected.color - coords = [projected.coords.row0.x, projected.coords.row0.y, projected.coords.row1.x, projected.coords.row1.y, projected.coords.row2.x, projected.coords.row2.y] + coords = [projected.coords.row1.x, projected.coords.row1.y, projected.coords.row2.x, projected.coords.row2.y, projected.coords.row3.x, projected.coords.row3.y] ui.canvas.create_polygon(coords, fill=from_rgb(r, r, r), outline="black") ui.canvas.update() diff --git a/matrix.py b/matrix.py index b5cf884..d6f22d5 100644 --- a/matrix.py +++ b/matrix.py @@ -1,38 +1,82 @@ -import vectors +import math +from vectors import Vector3d, Vector4d class Matrix4x4: - def __init__(self, row0, row1, row2, row3): - if not isinstance(row0, vectors.Vector4d): - raise ValueError("Row must be a Vector4d object.", row0) - if not isinstance(row1, vectors.Vector4d): + def __init__(self, row1, row2, row3, row4): + if not isinstance(row1, Vector4d): raise ValueError("Row must be a Vector4d object.", row1) - if not isinstance(row2, vectors.Vector4d): + if not isinstance(row2, Vector4d): raise ValueError("Row must be a Vector4d object.", row2) - if not isinstance(row3, vectors.Vector4d): + if not isinstance(row3, Vector4d): raise ValueError("Row must be a Vector4d object.", row3) + if not isinstance(row4, Vector4d): + raise ValueError("Row must be a Vector4d object.", row4) - self.row0 = row0 self.row1 = row1 self.row2 = row2 self.row3 = row3 + self.row4 = row4 def multiply_3d(self, vector3d): - x = vector3d.x * self.row0.x + vector3d.y * self.row1.x + vector3d.z * self.row2.x + self.row3.x - y = vector3d.x * self.row0.y + vector3d.y * self.row1.y + vector3d.z * self.row2.y + self.row3.y - z = vector3d.x * self.row0.z + vector3d.y * self.row1.z + vector3d.z * self.row2.z + self.row3.z - w = vector3d.x * self.row0.w + vector3d.y * self.row1.w + vector3d.z * self.row2.w + self.row3.w + x = vector3d.x * self.row1.x + vector3d.y * self.row2.x + vector3d.z * self.row3.x + self.row4.x + y = vector3d.x * self.row1.y + vector3d.y * self.row2.y + vector3d.z * self.row3.y + self.row4.y + z = vector3d.x * self.row1.z + vector3d.y * self.row2.z + vector3d.z * self.row3.z + self.row4.z + w = vector3d.x * self.row1.w + vector3d.y * self.row2.w + vector3d.z * self.row3.w + self.row4.w if w != 0: x /= w y /= w z /= w - return vectors.Vector3d(x, y, z) + return Vector3d(x, y, z) + + + def get_projection_matrix(fov_degrees, aspect_ratio, near_plane, far_plane): + fov_rad = 1 / math.tan(fov_degrees * 0.5 / 180.0 * math.pi) + + row1 = Vector4d(aspect_ratio * fov_rad, 0, 0, 0) + row2 = Vector4d(0, fov_rad, 0, 0) + row3 = Vector4d(0, 0, far_plane / (far_plane - near_plane), 1.0) + row4 = Vector4d(0, 0, -far_plane * near_plane / (far_plane - near_plane), 0) + + return Matrix4x4(row1, row2, row3, row4) + + def get_identity_matrix(): + row1 = Vector4d(1, 0, 0, 0) + row2 = Vector4d(0, 1, 0, 0) + row3 = Vector4d(0, 0, 1, 0) + row4 = Vector4d(0, 0, 0, 1) + + return Matrix4x4(row1, row2, row3, row4) + + def get_y_rotation_matrix(angle_rad): + row1 = Vector4d(math.cos(angle_rad), 0, math.sin(angle_rad), 0) + row2 = Vector4d(0, 1, 0, 0) + row3 = Vector4d(-math.sin(angle_rad), 0, math.cos(angle_rad), 0) + row4 = Vector4d(0, 0, 0, 1) + + return Matrix4x4(row1, row2, row3, row4) + + def get_z_rotation_matrix(angle_rad): + row1 = Vector4d(math.cos(angle_rad), math.sin(angle_rad), 0, 0) + row2 = Vector4d(-math.sin(angle_rad), math.cos(angle_rad), 0, 0) + row3 = Vector4d(0, 0, 0, 0) + row4 = Vector4d(0, 0, 1, 0) + + return Matrix4x4(row1, row2, row3, row4) + + def get_x_rotation_matrix(angle_rad): + row1 = Vector4d(1, 0, 0, 0) + row2 = Vector4d(0, math.cos(angle_rad), math.sin(angle_rad), 0) + row3 = Vector4d(0, -math.sin(angle_rad), math.cos(angle_rad), 0) + row4 = Vector4d(0, 0, 0, 1) + + return Matrix4x4(row1, row2, row3, row4) class Matrix3x3: - def __init__(self, row0, row1, row2): - self.row0 = row0 + def __init__(self, row1, row2, row3): self.row1 = row1 self.row2 = row2 + self.row3 = row3