from geometry.point import Point from geometry.validation import is_coordinates, expect_type from geometry.drawing import line_end_arrow from math import sqrt, atan from superturtle.movement import fly from turtle import setheading, forward class Vector: """A Vector represents movement in 2D space. Like a Point, a Vector is defined by x and y coordinates. However, Points and Vectors are different. Here are a few things you can do with Vectors which it doesn't make sense to do with Points: - Multiply them by a number (multiplying a vector by 2 means twice the movement) - Negate them (-v means moving in the opposite direction of v) - Find their length A Vector can be initialized in multiple ways: - With no value: Represents no motion at all: (0, 0). - With an ordered pair of numbers: The Vector will have those coordinates. - With a Point: The Vector will have the Point's x and y coordinates. """ def __init__(self, value=None): if value is None: self.x, self.y = (0, 0) elif is_coordinates(value): self.x, self.y = value elif isinstance(value, Point): self.x, self.y = value.x, value.y else: raise TypeError(f"Can't create a Vector from {value}.") def draw(self, origin=None): "Draws the vector." origin = origin or Point() expect_type(origin, Point) fly(origin.x, origin.y) θ = atan(self.y / self.x) setheading(θ) forward(self.mag()) line_end_arrow() def __str__(self): return f"〈{self.x}, {self.y}〉" def __repr__(self): return f"Vector(({self.x}, {self.y}))" def __eq__(self, other): """Implements equality checking with a == b. Two Vectors are equal if their coordinates are the same. A Vector can only be equal to another Vector. """ try: expect_type(other, Vector) return (self.x, self.y) == (other.x, other.y) except TypeError: return False def __neg__(self): """Implements negation with -a. """ return Vector((-self.x, -self.y)) def __add__(self, other): """Implements addition with a + b. Other can be: - a Vector: interpreted as combining the movement of self and other, returning a new Vector. - a Point: interpreted as moving the point in the direction of the Vector, returning a new Point. """ expect_type(other, (Point, Vector)) if isinstance(other, Vector): return Vector((self.x + other.x, self.y + other.y)) elif isinstance(other, Point): return Point((self.x + other.x, self.y + other.y)) def __sub__(self, other): """Implements subtraction with a - b. Other must be a Vector, interpreted as subtraction the movement of other from self and returning a new Vector. """ expect_type(other, Vector) return Vector((self.x - other.x, self.y - other.y)) def __mul__(self, other): """Implements multiplication with a * 2, interpreted as scaling the vector by other. Other must be numeric. """ expect_type(other, (int, float)) return Vector((self.x * other, self.y * other)) def __rmul__(self, other): """Implements multiplication with 2 * a. """ return self * other def __truediv__(self, other): """Implements division with a / b, interpreted as dividing the vector by other. Other must be numeric. """ expect_type(other, (int, float)) if other == 0: raise ZeroDivisionError("Can't divide a Vector by zero.") return Vector((self.x / other, self.y / other)) def __bool__(self): """Implements truthiness--whether an instance of Vector is treated as True or False. Any Vector other than the zero vector is True. """ return self.mag() != 0 def mag(self): """Returns the magnitude (length) of the vector, using the Pythagorean formula. """ return sqrt(self.x * self.x + self.y * self.y) def scale(self, mag): """Returns a Vector pointing in the same direction with the given magnitude. """ expect_type(mag, (int, float)) if self.mag() > 0: return self * (mag / self.mag()) else: raise ValueError("Can't scale the zero vector.") def normalize(self): """Returns a Vector pointing in the same direction with a magnitude of 1. """ return self.scale(1) def dot(self, other): """Computes the dot product of this vector with another. """ expect_type(other, Vector) return self.x * other.x + self.y * other.y def project(self, other): """Projects this vector onto another. """ return other.scale(self.dot(other.normalize()))