import math
[docs]
class Vector2:
__slots__ = ("x", "y")
def __init__(self, x=0.0, y=0.0):
self.x = x
self.y = y
# -- Arithmetic operators ------------------------------------------------
def __add__(self, other):
if isinstance(other, Vector2):
return Vector2(self.x + other.x, self.y + other.y)
return Vector2(self.x + other, self.y + other)
def __radd__(self, other):
return Vector2(other + self.x, other + self.y)
def __iadd__(self, other):
if isinstance(other, Vector2):
self.x += other.x
self.y += other.y
else:
self.x += other
self.y += other
return self
def __sub__(self, other):
if isinstance(other, Vector2):
return Vector2(self.x - other.x, self.y - other.y)
return Vector2(self.x - other, self.y - other)
def __rsub__(self, other):
return Vector2(other - self.x, other - self.y)
def __isub__(self, other):
if isinstance(other, Vector2):
self.x -= other.x
self.y -= other.y
else:
self.x -= other
self.y -= other
return self
def __mul__(self, other):
if isinstance(other, Vector2):
return Vector2(self.x * other.x, self.y * other.y)
return Vector2(self.x * other, self.y * other)
def __rmul__(self, other):
return Vector2(other * self.x, other * self.y)
def __imul__(self, other):
if isinstance(other, Vector2):
self.x *= other.x
self.y *= other.y
else:
self.x *= other
self.y *= other
return self
def __truediv__(self, other):
if isinstance(other, Vector2):
return Vector2(self.x / other.x, self.y / other.y)
return Vector2(self.x / other, self.y / other)
def __itruediv__(self, other):
if isinstance(other, Vector2):
self.x /= other.x
self.y /= other.y
else:
self.x /= other
self.y /= other
return self
def __neg__(self):
return Vector2(-self.x, -self.y)
# -- Comparison and hashing ----------------------------------------------
def __eq__(self, other):
if isinstance(other, Vector2):
return self.x == other.x and self.y == other.y
return NotImplemented
def __hash__(self):
return hash((self.x, self.y))
def __bool__(self):
return self.x != 0.0 or self.y != 0.0
# -- Iteration and representation ----------------------------------------
def __iter__(self):
yield self.x
yield self.y
def __getitem__(self, index):
if index == 0:
return self.x
if index == 1:
return self.y
raise IndexError(f"Vector2 index out of range: {index}")
def __len__(self):
return 2
def __repr__(self):
return f"Vector2({self.x}, {self.y})"
# -- Core math -----------------------------------------------------------
[docs]
def magnitude(self):
return math.sqrt(self.x ** 2 + self.y ** 2)
[docs]
def sqr_magnitude(self):
return self.x ** 2 + self.y ** 2
[docs]
def normalize(self):
m = self.magnitude()
if m == 0:
return Vector2(0, 0)
return Vector2(self.x / m, self.y / m)
normalized = normalize
[docs]
def normalize_ip(self):
"""Normalize this vector in-place. Returns self for chaining."""
m = self.magnitude()
if m != 0:
self.x /= m
self.y /= m
return self
[docs]
def dot(self, other):
return self.x * other.x + self.y * other.y
[docs]
def cross(self, other):
return self.x * other.y - self.y * other.x
[docs]
def copy(self):
return Vector2(self.x, self.y)
# -- Utility methods -----------------------------------------------------
[docs]
def distance_to(self, other):
dx = self.x - other.x
dy = self.y - other.y
return math.sqrt(dx * dx + dy * dy)
[docs]
def distance_to_squared(self, other):
dx = self.x - other.x
dy = self.y - other.y
return dx * dx + dy * dy
[docs]
def lerp(self, other, t):
t = max(0.0, min(1.0, t))
return Vector2(
self.x + (other.x - self.x) * t,
self.y + (other.y - self.y) * t
)
[docs]
def angle(self):
return math.degrees(math.atan2(self.y, self.x))
[docs]
def angle_to(self, other):
return math.degrees(math.atan2(other.y - self.y, other.x - self.x))
[docs]
def rotate(self, degrees):
rad = math.radians(degrees)
cos_a = math.cos(rad)
sin_a = math.sin(rad)
return Vector2(
self.x * cos_a - self.y * sin_a,
self.x * sin_a + self.y * cos_a
)
[docs]
def reflect(self, normal):
d = 2.0 * self.dot(normal)
return Vector2(self.x - d * normal.x, self.y - d * normal.y)
# -- Class-level constants -----------------------------------------------
[docs]
@classmethod
def zero(cls):
return cls(0.0, 0.0)
[docs]
@classmethod
def one(cls):
return cls(1.0, 1.0)
[docs]
@classmethod
def up(cls):
return cls(0.0, -1.0)
[docs]
@classmethod
def down(cls):
return cls(0.0, 1.0)
[docs]
@classmethod
def left(cls):
return cls(-1.0, 0.0)
[docs]
@classmethod
def right(cls):
return cls(1.0, 0.0)