def test_dot_product(self): """ Testing * operator for dot_product """ vec_a = Vec3(1, 2, 3) vec_b = Vec3(0, 1, 3) dot_product = vec_a * vec_b self.assertEqual(11, dot_product, "Asserting dot_product")
def test_not_equals(self): """ Testing != operator """ vec_a = Vec3(2, 3, 4) vec_b = Vec3(5, 6, 7) self.assertFalse(vec_a != vec_a, "Asserting vec_a == vec_a") self.assertTrue(vec_a != vec_b, "Asserting vec_a == vec_b")
def test_addition(self): """ Testing + operator """ vec_a = Vec3(1, 2, 3) vec_b = Vec3(0, 1, 3) vec_c = vec_a + vec_b self.assertEqual(1, vec_c.x, "Asserting vec3.x") self.assertEqual(3, vec_c.y, "Asserting vec3.y") self.assertEqual(6, vec_c.z, "Asserting vec3.z")
def test_cross_product(self): """ Testing // operator """ vec_a = Vec3(2, 3, 4) vec_b = Vec3(5, 6, 7) vec_c = vec_a // vec_b self.assertEqual(-3, vec_c.x, "Asserting vec3.x") self.assertEqual(6, vec_c.y, "Asserting vec3.y") self.assertEqual(-3, vec_c.z, "Asserting vec3.z")
def test_subtraction(self): """ Testing - operator """ vec_a = Vec3(5, 4, 3) vec_b = Vec3(3, 4, 5) vec_c = vec_a - vec_b self.assertEqual(2, vec_c.x, "Asserting vec3.x") self.assertEqual(0, vec_c.y, "Asserting vec3.y") self.assertEqual(-2, vec_c.z, "Asserting vec3.z")
def test_scalar(self): """ Testing * operator for scalar multiplication """ vec = Vec3(5, 2, 3) result = vec * 3 self.assertEqual(15, result.x, "Asserting vec3.x") self.assertEqual(6, result.y, "Asserting vec3.y") self.assertEqual(9, result.z, "Asserting vec3.z") vec = Vec3(2, 3, 4) result = 5 * vec self.assertEqual(10, result.x, "Asserting vec3.x") self.assertEqual(15, result.y, "Asserting vec3.y") self.assertEqual(20, result.z, "Asserting vec3.z")
def test_normalize(self): """ Testing .normalize() """ vec = Vec3(4, 3, 0).normalize() self.assertEqual(0.8, vec.x, "Asserting vec3.x") self.assertEqual(0.6, vec.y, "Asserting vec3.y") self.assertEqual(0, vec.z, "Asserting vec3.z")
def __mul__(self, other: Union[Vec3, Mat4x4]) -> Union[Vec3, Mat4x4]: """ Multiplication of a matrix and vector results in a vector. :param other: Vec3 or Mat4x4 :return: Vec3 or Mat4x4 """ if isinstance(other, Vec3): x = other.x * self.m[0][0] + other.y * self.m[1][ 0] + other.z * self.m[2][0] + self.m[3][0] y = other.x * self.m[0][1] + other.y * self.m[1][ 1] + other.z * self.m[2][1] + self.m[3][1] z = other.x * self.m[0][2] + other.y * self.m[1][ 2] + other.z * self.m[2][2] + self.m[3][2] w = other.x * self.m[0][3] + other.y * self.m[1][ 3] + other.z * self.m[2][3] + self.m[3][3] if w != 0.: x /= w y /= w z /= w return Vec3(x, y, z) elif isinstance(other, Mat4x4): _LOGGER.warning("DEPRECATED METHOD. DON'T USE IT") return Mat4x4.multiply_matrix(self, other)
def __init__(self, *, near: float, far: float, fov: float, screen_height: int, screen_width: int): """ Set up all variables needed for the projection matrix :param near: float representing distance to near plane :param far: float representing distance to far plane :param fov: float representing field-of-view :param screen_height: int representing height of the screen :param screen_width: int representing width of the screen """ self.near = near, self.far = far, self.fov = fov, self.screen_height = screen_height self.screen_width = screen_width self.aspect_ration = float(screen_height / screen_width) self.fov_rad = 1.0 / tan(fov * 0.5 / 180.0 * pi) self.theta = pi / 4 self.time_diff = 1 self.camera = Camera(Vec3(0, 0, -10)) self.projection_matrix = self._make_projection_matrix(far, near) # Get objects for the scene self.objects = get_objects_for_scene()
def read_obj_model(path: str) -> [Triangle]: """ Read .obj file and return a list of Triangles, a Model. :param path: Path to the .obj file :return: Model represented as a list of Triangle objects """ v = [] # Vertices f = [] # Faces/Triangles with open(path) as file: lines = file.readlines() for line in lines: line = line.split() # Remove whitespaces # Based of first char put in appropriate container if 'v' in line: v.append(Vec3(float(line[1]), float(line[2]), float(line[3]))) elif 'f' in line: f.append( Triangle(v[int(line[1]) - 1], v[int(line[2]) - 1], v[int(line[3]) - 1])) else: continue return f
def test_vector_multiplication_with_vec3(self): """ Testing * operator with Vec3 """ vec = Vec3(1, 2, 3) matrix = Mat4x4() matrix.m[0] = [4, 3, 1, 0] matrix.m[2] = [2, 2, 2, 0] result = matrix * vec self.assertEqual(10, result.x, "Asserting vec.x from matrix-vector multiplication") self.assertEqual(9, result.y, "Asserting vec.y from matrix-vector multiplication") self.assertEqual(7, result.z, "Asserting vec.z from matrix-vector multiplication")
def render_frame(self, window: Canvas, time_diff: float) -> Canvas: # Clear screen window.delete("all") # update time_diff self.time_diff = time_diff # update camera position self.update_camera_position() # Get objects in scene objects = copy.deepcopy(self.objects) # Angle for rotation # self.theta += time_diff * 1.0 # Setup Z and X rotation matrices z_rotate = Mat4x4.z_rotation_matrix(3 * pi) x_rotate = Mat4x4.x_rotation_matrix(2 * pi) # Setup Translation matrix translation_matrix = Mat4x4.translation_matrix(0.0, 0.0, 0.0) # Setup World matrix world_matrix = Mat4x4.multiply_matrix(z_rotate, x_rotate) world_matrix = Mat4x4.multiply_matrix(world_matrix, translation_matrix) # Make point_at matrix up_vector = Vec3(0, 1, 0) target_vector = Vec3(0, 0, 1) camera_rotation = Mat4x4.y_rotation_matrix(self.camera.yaw) self.camera.look_direction = camera_rotation * target_vector target_vector = self.camera.position + self.camera.look_direction camera_matrix = self._point_at_matrix(self.camera.position, target_vector, up_vector) camera_view = self._quick_inverse_matrix(camera_matrix) # Triangle to be drawn triangles_to_draw = [] # Loop on objects in scene for obj in objects: # Loop on triangles in an object for tri in obj: # Perform Translate-Rotate-Scale matrix multiplication on triangle tri_transformed = tri tri_transformed.p[0] = world_matrix * tri.p[0] tri_transformed.p[1] = world_matrix * tri.p[1] tri_transformed.p[2] = world_matrix * tri.p[2] # Get normal of triangle line_a = tri_transformed.p[1] - tri_transformed.p[0] line_b = tri_transformed.p[2] - tri_transformed.p[0] normal = line_a // line_b normal.normalize() if normal * (tri_transformed.p[0] - self.camera.position) < 0.0: # Illuminate triangle light_direction = Vec3( 0.0, 0.0, -1.0).normalize() # towards the camera dot_product = max(0.1, light_direction * normal) tri_transformed.angle_to_light = dot_product # Convert World Space into View Space tri_viewed = tri_transformed tri_viewed.p[0] = camera_view * tri_transformed.p[0] tri_viewed.p[1] = camera_view * tri_transformed.p[1] tri_viewed.p[2] = camera_view * tri_transformed.p[2] # Project triangles tri_projected = self._project_triangle(tri_viewed) # Scale triangle into view tri_scaled = self._scale_triangle(tri_projected) # Store triangle triangles_to_draw.append(tri_scaled) # Sort the triangles using *z-buffer* triangles_to_draw.sort(key=lambda x: (x.p[0].z + x.p[1].z + x.p[2].z) / 3.0, reverse=True) # Draw triangles to screen for triangle in triangles_to_draw: self._draw_triangle(triangle, window) # Add debug info to window camera_text = (f"Camera:\n" f" X: {self.camera.position.x}\n" f" Y: {self.camera.position.y}\n" f" Z: {self.camera.position.z}\n" f" Yaw: {self.camera.yaw}") triangle_count = 0 for obj in objects: triangle_count = triangle_count + len(obj) triangle_text = ("Triangles:\n" f" Total: {triangle_count}\n" f" Drawn: {len(triangles_to_draw)}") window.create_text(5, 5, anchor=NW, text=camera_text, fill="red") window.create_text(5, 100, anchor=NW, text=triangle_text, fill="red") return window
def __init__(self, position: Vec3): self.position = position self.origin = copy.deepcopy(self.position) self.look_direction = Vec3(0, 0, 1) self.yaw = 0 self.move_direction = None