142 lines
4.9 KiB
Python
142 lines
4.9 KiB
Python
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()))
|