"""
vector2
Author: Timothy Moore
Defines a simple two-dimensional, mutable vector.
"""
import math
[docs]class Vector2(object):
"""
Define a simple two-dimensional, mutable vector.
.. important::
Equality is not overriden on vectors, because it is expected that
vectors will be used mutably by directly modifying x and y. However, all
functions on vectors are immutable (they return a copy)
:ivar x: The first component of this vector.
:vartype x: :class:`numbers.Number`
:ivar y: The second component of this vector.
:vartype y: :class:`numbers.Number`
"""
[docs] def __init__(self, *args, **kwargs):
"""
Create a new Vector2 from the two components.
Accepts a pair of unnamed parameters, a pair of named x, y parameters,
another Vector2, or a tuple with 2 numerics. Examples of each:
.. code-block:: python
from pygorithm.geometry import vector2
# A pair of unnamed parameters
vec1 = vector2.Vector2(0, 5)
# A pair of named parameters
vec2 = vector2.Vector2(x = 0, y = 5)
# Another vector2
vec3 = vector2.Vector2(vec2)
# A tuple with two numerics
vec4 = vector2.Vector2( (0, 5) )
:param args: unnamed arguments (purpose guessed by order)
:param kwargs: named arguments (purpose known by name)
"""
if len(args) == 2:
self.x = args[0]
self.y = args[1]
elif len(args) == 1:
if type(args[0]) == tuple:
self.x = args[0][0]
self.y = args[0][1]
else:
self.x = args[0].x
self.y = args[0].y
else:
assert(len(args) == 0)
self.x = kwargs['x']
self.y = kwargs['y']
[docs] def __add__(self, other):
"""
Adds the two vectors component wise.
Example:
.. code-block:: python
from pygorithm.geometry import vector2
vec1 = vector2.Vector2(0, 3)
vec2 = vector2.Vector2(2, 4)
vec3 = vec1 + vec2
# prints <2, 7>
print(vec3)
:param other: the vector to add to this one
:type other: :class:`pygorithm.geometry.vector2.Vector2`
:returns: a new vector that is the sum of self and other
:rtype: :class:`pygorithm.geometry.vector2.Vector2`
"""
return Vector2(self.x + other.x, self.y + other.y)
[docs] def __sub__(self, other):
"""
Subtract the two vectors component wise.
Example:
.. code-block:: python
from pygorithm.geometry import vector2
vec1 = vector2.Vector2(5, 5)
vec2 = vector2.Vector2(2, 3)
vec3 = vec1 - vec2
vec4 = vec2 - vec1
# prints <3, 2>
print(vec3)
# prints <2, 3>
print(vec4)
:param other: the vector to subtract from this one
:type other: :class:`pygorithm.geometry.vector2.Vector2`
:returns: a new vector two that is the difference of self and other
:rtype: :class:`pygorithm.geometry.vector2.Vector2`
"""
return Vector2(self.x - other.x, self.y - other.y)
[docs] def __mul__(self, scale_factor):
"""
Scale the vector by the specified factor.
.. caution::
This will never perform a dot product. If scale_factor is a Vector2, an
exception is thrown.
Example:
.. code-block:: python
from pygorithm.geometry import vector2
vec1 = vector2.Vector2(4, 8)
vec2 = vec1 * 0.5
# prints <2, 4>
print(vec2)
:param: scale_factor the amount to scale this vector by
:type scale_factor: :class:`numbers.Number`
:returns: a new vector that is self scaled by scale_factor
:rtype: :class:`pygorithm.geometry.vector2.Vector2`
:raises TypeError: if scale_factor is a Vector2
"""
if type(scale_factor) == Vector2:
raise TypeError('scale_factor cannot be a Vector2 (use dot!)')
return Vector2(self.x * scale_factor, self.y * scale_factor)
[docs] def __rmul__(self, scale_factor):
"""
Scale the vector by the specified factor.
.. caution::
This will never perform a dot product. If scale_factor is a Vector2, an
exception is thrown.
Example:
.. code-block:: python
from pygorithm.geometry import vector2
vec1 = vector2.Vector2(4, 8)
vec2 = 2 * vec1
# prints <8, 16>
print(vec2)
:param: scale_factor the amount to scale this vector by
:type scale_factor: :class:`numbers.Number`
:returns: a new vector that is self scaled by scale_factor
:rtype: :class:`pygorithm.geometry.vector2.Vector2`
:raises TypeError: if scale_factor is a Vector2
"""
if type(scale_factor) == Vector2:
raise TypeError('scale_factor cannot be a Vector2 (use dot!)')
return Vector2(self.x * scale_factor, self.y * scale_factor)
[docs] def __repr__(self):
"""
Create an unambiguous representation of this vector
Example:
.. code-block:: python
from pygorithm.geometry import vector2
vec = vector2.Vector2(3, 5)
# prints vector2(x=3, y=5)
print(repr(vec))
:returns: an unambiguous representation of this vector
:rtype: string
"""
return "vector2(x={}, y={})".format(self.x, self.y)
[docs] def __str__(self):
"""
Create a human-readable representation of this vector.
Rounds to 3 decimal places if there are more.
Example:
.. code-block:: python
from pygorithm.geometry import vector2
vec = vector2.Vector2(7, 11)
# prints <7, 11>
print(str(vec))
# also prints <7, 11>
print(vec)
:returns: a human-readable representation of this vector
:rtype: string
"""
pretty_x = round(self.x * 1000) / 1000
if pretty_x == math.floor(pretty_x):
pretty_x = math.floor(pretty_x)
pretty_y = round(self.y * 1000) / 1000
if pretty_y == math.floor(pretty_y):
pretty_y = math.floor(pretty_y)
return "<{}, {}>".format(pretty_x, pretty_y)
[docs] def dot(self, other):
"""
Calculate the dot product between this vector and other.
The dot product of two vectors is calculated as so::
Let v1 be a vector such that v1 = <v1_x, v1_y>
Let v2 be a vector such that v2 = <v2_x, v2_y>
v1 . v2 = v1_x * v2_x + v1_y * v2_y
Example:
.. code-block:: python
from pygorithm.geometry import vector2
vec1 = vector2.Vector2(3, 5)
vec2 = vector2.Vector2(7, 11)
dot_12 = vec1.dot(vec2)
# prints 76
print(dot_12)
:param other: the other vector
:type other: :class:`pygorithm.geometry.vector2.Vector2`
:returns: the dot product of self and other
:rtype: :class:`numbers.Number`
"""
return self.x * other.x + self.y * other.y
[docs] def cross(self, other):
"""
Calculate the z-component of the cross product between this vector and other.
The cross product of two vectors is calculated as so::
Let v1 be a vector such that v1 = <v1_x, v1_y>
Let v2 be a vector such that v2 = <v2_x, v2_y>
v1 x v2 = v1.x * v2.y - v1.y * v2.x
.. caution::
This is the special case of a cross product in 2 dimensions returning 1
value. This is really a vector in the z direction!
"""
return self.x * other.y - self.y * other.x
[docs] def rotate(self, *args, **kwargs):
"""
The named argument "degrees" or "radians" may be passed in to rotate
this vector by the specified amount in degrees (or radians),
respectively. If both are omitted, the first unnamed argument is
assumed to be the amount to rotate in radians.
Additionally, the named argument "about" may be passed in to specify
about what the vector should be rotated. If omitted then the first
unconsumed unnamed argument is assumed to be the vector. If there are
no unconsumed unnamed arguments then the origin is assumed.
Examples:
.. code-block:: python
from pygorithm.geometry import vector2
import math
vec1 = vector2.Vector2(1, 0)
vec2 = vec1.rotate(math.pi * 0.25)
# prints <0.707, 0.707>
print(vec2)
vec3 = vec1.rotate(degrees = 45)
# prints <0.707, 0.707>
print(vec3)
# The following operations are all identical
vec4 = vec1.rotate(math.pi, vector2.Vector2(1, 1))
vec5 = vec1.rotate(radians = math.pi, about = vector2.Vector2(1, 1))
vec6 = vec1.rotate(degrees = 180, about = vector2.Vector2(1, 1))
vec7 = vec1.rotate(vector2.Vector2(1, 1), degrees = 180)
# prints <1, 2>
print(vec4)
:param args: the unnamed arguments (purpose guessed by position)
:param kwargs: the named arguments (purpose known by name)
:returns: the new vector formed by rotating this vector
:rtype: :class:`pygorithm.geometry.vector2.Vector2`
"""
args_counter = 0
deg_rads = None
about = None
if 'radians' in kwargs:
deg_rads = kwargs['radians']
elif 'degrees' in kwargs:
deg_rads = kwargs['degrees'] * math.pi / 180
else:
deg_rads = args[args_counter]
args_counter = args_counter + 1
if 'about' in kwargs:
about = kwargs['about']
else:
if len(args) > args_counter:
about = args[args_counter]
fixed_x = self.x
fixed_y = self.y
if about is not None:
fixed_x -= about.x
fixed_y -= about.y
rotated_x = fixed_x * math.cos(deg_rads) - fixed_y * math.sin(deg_rads)
rotated_y = fixed_y * math.cos(deg_rads) + fixed_x * math.sin(deg_rads)
final_x = rotated_x
final_y = rotated_y
if about is not None:
final_x += about.x
final_y += about.y
return Vector2(final_x, final_y)
[docs] def normalize(self):
"""
Create the normalized version of this vector
The normalized version will go in the same direction but will
have magnitude of 1.
.. note::
This will never return self, even if this vector is already
normalized.
Example:
.. code-block:: python
from pygorithm.geometry import vector2
vec1 = vector2.Vector2(2, 0)
vec2 = vec1.normalize()
# prints <1, 0>
print(vec2)
:returns: a new normalized version of this vector
:rtype: :class:`pygorithm.geometry.vector2.Vector2`
"""
return self * (1 / self.magnitude())
[docs] def magnitude_squared(self):
"""
Calculate the square of the magnitude of this vector.
Example:
.. code-block:: python
from pygorithm.geometry import vector2
vec1 = vector2.Vector2(5, 12)
magn_sq = vec1.magnitude_squared()
# prints 169 (13^2)
print(magn_sq)
:returns: square of the magnitude of this vector
:rtype: :class:`numbers.Number`
"""
return self.x * self.x + self.y * self.y
[docs] def magnitude(self):
"""
Calculate the magnitude of this vector
.. note::
It is substantially faster to operate on magnitude squared
where possible.
Example:
.. code-block:: python
from pygorithm.geometry import vector2
vec1 = vector2.Vector2(3, 4)
magn = vec1.magnitude()
# prints 5
print(magn)
:returns: magnitude of this vector
:rtype: :class:`numbers.Number`
"""
return math.sqrt(self.magnitude_squared())