"""
A vector implementation.
"""
from typing import Union
from rubato.utils import Math
import math
[docs]class Vector:
"""
A Vector object that defines a 2D point in space
Attributes:
x (Union[float, int]): The x coordinate.
y (Union[float, int]): The y coordinate.
"""
[docs] def __init__(self, x: Union[float, int] = 0, y: Union[float, int] = 0):
"""
Initializes a vector.
Args:
x: The x coordinate. Defaults to 0.
y: The y coordinate. Defaults to 0.
"""
self.x, self.y = x, y
[docs] def translate(self, x: Union[float, int], y: Union[float, int]):
"""
Translates the vector's x y and z coordinates by some constants.
Args:
x: The change in x.
y: The change in y.
"""
self.x, self.y = self.x + x, self.y + y
[docs] def offset(self, other: "Vector") -> "Vector":
"""
Offsets the x and y coordinates of a vector by those of another vector.
Args:
other: Another vector.
Returns:
Vector: A new vector with the translated x and y coordinates.
"""
return Vector(self.x - other.x, self.y - other.y)
[docs] def to_tuple(self) -> tuple:
"""
Returns the x and y coordinates of the vector as a tuple.
"""
return self.x, self.y
[docs] @staticmethod
def from_tuple(coords: tuple) -> "Vector":
"""
Returns a Vector from a coordinate tuple
Args:
coords: tuple with an x and y coordinate [float | int].
Returns: Vector with specified coordinates.
"""
return Vector(coords[0], coords[1])
[docs] def dot(self, other: "Vector") -> Union[float, int]:
"""
Takes the dot product of vectors.
Args:
other: The other vector.
Returns:
Union[float, int]: The resulting dot product.
"""
return self.x * other.x + self.y * other.y
[docs] def cross(self, other: "Vector") -> Union[float, int]:
"""
Takes the cross product of vectors.
Args:
other: The other vector.
Returns:
Union[float, int]: The resulting cross product.
"""
return self.x * other.y - self.y * other.x
[docs] def crossp(self) -> "Vector":
"""
Does `Vector(self.y, -self.x)`.
Returns:
Vector: The resulting vector.
"""
return Vector(self.y, -self.x)
[docs] def pow(self, num: Union[float, int]) -> "Vector":
"""
Takes the power of the elements in the vector to the num.
Args:
num: The exponent to use.
Returns:
Vector: The new vector.
"""
return Vector(self.x**num, self.y**num)
def __str__(self) -> str:
return f"({self.x}, {self.y})"
def __pow__(self, other: any) -> "Vector":
if isinstance(other, (int, float)):
return Vector(self.x ** other, self.y ** other)
if isinstance(other, Vector):
return Vector(self.x ** other.x, self.y ** other.y)
def __mul__(self, other: any) -> "Vector":
if isinstance(other, (int, float)):
return Vector(self.x * other, self.y * other)
if isinstance(other, Vector):
return Vector(self.x * other.x, self.y * other.y)
def __add__(self, other: any) -> "Vector":
if isinstance(other, (int, float)):
return Vector(self.x + other, self.y + other)
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
__rmul__ = __mul__
__radd__ = __add__
def __sub__(self, other: any) -> "Vector":
if isinstance(other, (int, float)):
return Vector(self.x - other, self.y - other)
if isinstance(other, Vector):
return Vector(self.x - other.x, self.y - other.y)
def __rsub__(self, other: int | float) -> "Vector":
return Vector(other - self.x, other - self.y)
def __truediv__(self, other: any) -> "Vector":
if isinstance(other, (int, float)):
return Vector(self.x / other, self.y / other)
if isinstance(other, Vector):
return Vector(self.x / other.x, self.y / other.y)
def __rtruediv__(self, other: int | float) -> "Vector":
return Vector(other / self.x, other / self.y)
def __neg__(self) -> "Vector":
return Vector(-self.x, -self.y)
@classmethod
@property
def zero(cls):
return Vector(0, 0)
@classmethod
@property
def one(cls):
return Vector(1, 1)
@classmethod
@property
def two(cls):
return Vector(2, 2)
@classmethod
@property
def up(cls):
return Vector(0, -1)
@classmethod
@property
def left(cls):
return Vector(-1, 0)
@classmethod
@property
def down(cls):
return Vector(0, 1)
@classmethod
@property
def right(cls):
return Vector(1, 0)
@classmethod
@property
def infinity(cls):
return Vector(Math.INFINITY, Math.INFINITY)
def _equals(self, v: "Vector") -> bool:
return self.y == v.y and self.x == v.x
def __eq__(self, other: "Vector") -> bool:
return (other is None or not isinstance(other, Vector)
or self._equals(other))
[docs] def clamp(self,
lower: Union["Vector", int, float],
upper: Union["Vector", int, float],
absolute: bool = False):
"""
Clamps x and y between the two values given.
Args:
lower: The lower bound.
upper: The upper bound.
absolute: Whether to clamp the absolute value of the vector
instead of the actual value.
"""
if not isinstance(lower, Vector):
lower = Vector(*lower)
if not isinstance(upper, Vector):
upper = Vector(*upper)
if not absolute:
self.x = Math.clamp(self.x, lower.x, upper.x)
self.y = Math.clamp(self.y, lower.y, upper.y)
else:
self.x = Math.abs_clamp(self.x, lower.x, upper.x)
self.y = Math.abs_clamp(self.y, lower.y, upper.y)
@property
def magnitude(self) -> float:
"""Returns the magnitude of the vector."""
return (self.x**2 + self.y**2)**.5
@property
def mag(self) -> float:
"""Returns the squared magnitude of the vector."""
return self.x**2 + self.y**2
@magnitude.setter
def magnitude(self, value: Union[float, int]):
"""Sets the magnitude of a vector."""
mag = self.magnitude
if mag == 0: return
ratio = value / mag
self.x *= ratio
self.y *= ratio
[docs] def normalize(self):
"""Normalize the vector."""
self.magnitude = 1
[docs] def unit(self) -> "Vector":
"""Returns the unit vector of this vector."""
copy = self.clone()
copy.normalize()
return copy
[docs] @staticmethod
def from_radial(angle: float, magnitude: float) -> "Vector":
"""
Gives you a Vector from the given direction and distance.
Args:
angle: Direction of vector in radians.
magnitude: Length of vector.
Returns:
Vector: Vector from the given direction and distance
"""
return Vector(math.cos(angle) * magnitude, math.sin(angle) * magnitude)
@property
def angle(self) -> float:
"""Returns the angle of the vector"""
return math.atan2(self.y, self.x)
def transform(self, scale, rotation):
new_vector = self.clone()
if rotation != 0:
hyp, angle = self.magnitude, self.angle + rotation * math.pi / 180
new_vector.x, new_vector.y = math.cos(angle) * hyp, math.sin(
angle) * hyp
new_vector.x *= scale
new_vector.y *= scale
return new_vector
[docs] def invert(self, axis: str):
"""
Inverts the vector in the axis given
Args:
axis: The axis to invert the vector in (x or y).
Raises:
ValueError: The value for axis is not "x" or "y"
"""
if axis == "x":
self.x = -self.x
elif axis == "y":
self.y = -self.y
else:
raise ValueError(f"{axis} is not a valid axis")
[docs] def to_int(self) -> "Vector":
"""Returns a new vector with values that are ints."""
return Vector(int(self.x), int(self.y))
[docs] def clone(self) -> "Vector":
"""Returns a copy of the vector."""
return Vector(self.x, self.y)
[docs] def lerp(self, target: "Vector", t: float) -> "Vector":
"""
Changes its values x and y to fit the target vector by amount t.
Args:
target: the target velocity.
t: the amount you lerp between 0 and 1.
Returns:
Vector: The resulting vector.
"""
t = Math.clamp(t, 0, 1)
return Vector(Math.lerp(self.x, target.x, t),
Math.lerp(self.y, target.y, t))
[docs] def round(self, decimal_places: int):
"""
Rounds x and y to decimal_places
Args:
decimal_places: the amount of decimal places rounded to.
"""
self.x = round(self.x, decimal_places)
self.y = round(self.y, decimal_places)
[docs] def ceil(self) -> "Vector":
"""
Returns the cieled vector.
"""
return Vector(math.ceil(self.x), math.ceil(self.y))
[docs] def direction_to(self, vector: "Vector") -> float:
"""
Treating vectors as points the direction to the other vector from the
current vector.
Args:
vector: The other vector.
Returns:
float: The direction to the new vector in radians.
"""
d_x = self.x - vector.x
d_y = self.y - vector.y
direction = math.atan2(d_y, d_x)
return direction
[docs] def distance_to(self, vector: "Vector") -> float:
"""
Treating vectors as points the distance to the other vector from
the current vector.
Args:
vector: The other vector.
Returns:
float: Distance to new vector.
"""
d_x = self.x - vector.x
d_y = self.y - vector.y
distance = math.sqrt(d_x**2 + d_y**2)
return distance
[docs] def absolute(self) -> "Vector":
"""
Returns a vector representing the absolute values of the current vector
"""
return Vector(abs(self.x), abs(self.y))
def __gt__(self, other) -> bool:
return self.x > other.x and self.y > other.y
def __lt__(self, other) -> bool:
return self.x < other.x and self.y < other.y
def __ge__(self, other) -> bool:
return self.x >= other.x and self.y >= other.y
def __le__(self, other) -> bool:
return self.x <= other.x and self.y <= other.y