413 lines
10 KiB
Python
413 lines
10 KiB
Python
'''Vector
|
|
======
|
|
|
|
The :class:`Vector` represents a 2D vector (x, y).
|
|
Our implementation is built on top of a Python list.
|
|
|
|
An example of constructing a Vector::
|
|
|
|
>>> # Construct a point at 82,34
|
|
>>> v = Vector(82, 34)
|
|
>>> v[0]
|
|
82
|
|
>>> v.x
|
|
82
|
|
>>> v[1]
|
|
34
|
|
>>> v.y
|
|
34
|
|
|
|
>>> # Construct by giving a list of 2 values
|
|
>>> pos = (93, 45)
|
|
>>> v = Vector(pos)
|
|
>>> v[0]
|
|
93
|
|
>>> v.x
|
|
93
|
|
>>> v[1]
|
|
45
|
|
>>> v.y
|
|
45
|
|
|
|
|
|
Optimized usage
|
|
---------------
|
|
|
|
Most of the time, you can use a list for arguments instead of using a
|
|
Vector. For example, if you want to calculate the distance between 2
|
|
points::
|
|
|
|
a = (10, 10)
|
|
b = (87, 34)
|
|
|
|
# optimized method
|
|
print('distance between a and b:', Vector(a).distance(b))
|
|
|
|
# non-optimized method
|
|
va = Vector(a)
|
|
vb = Vector(b)
|
|
print('distance between a and b:', va.distance(vb))
|
|
|
|
|
|
Vector operators
|
|
----------------
|
|
|
|
The :class:`Vector` supports some numeric operators such as +, -, /::
|
|
|
|
>>> Vector(1, 1) + Vector(9, 5)
|
|
[10, 6]
|
|
|
|
>>> Vector(9, 5) - Vector(5, 5)
|
|
[4, 0]
|
|
|
|
>>> Vector(10, 10) / Vector(2., 4.)
|
|
[5.0, 2.5]
|
|
|
|
>>> Vector(10, 10) / 5.
|
|
[2.0, 2.0]
|
|
|
|
|
|
You can also use in-place operators::
|
|
|
|
>>> v = Vector(1, 1)
|
|
>>> v += 2
|
|
>>> v
|
|
[3, 3]
|
|
>>> v *= 5
|
|
[15, 15]
|
|
>>> v /= 2.
|
|
[7.5, 7.5]
|
|
|
|
'''
|
|
|
|
__all__ = ('Vector', )
|
|
|
|
import math
|
|
|
|
|
|
class Vector(list):
|
|
'''Vector class. See module documentation for more information.
|
|
'''
|
|
|
|
def __init__(self, *largs):
|
|
if len(largs) == 1:
|
|
super(Vector, self).__init__(largs[0])
|
|
elif len(largs) == 2:
|
|
super(Vector, self).__init__(largs)
|
|
else:
|
|
raise Exception('Invalid vector')
|
|
|
|
def _get_x(self):
|
|
return self[0]
|
|
|
|
def _set_x(self, x):
|
|
self[0] = x
|
|
|
|
x = property(_get_x, _set_x)
|
|
''':attr:`x` represents the first element in the list.
|
|
|
|
>>> v = Vector(12, 23)
|
|
>>> v[0]
|
|
12
|
|
>>> v.x
|
|
12
|
|
'''
|
|
|
|
def _get_y(self):
|
|
return self[1]
|
|
|
|
def _set_y(self, y):
|
|
self[1] = y
|
|
|
|
y = property(_get_y, _set_y)
|
|
''':attr:`y` represents the second element in the list.
|
|
|
|
>>> v = Vector(12, 23)
|
|
>>> v[1]
|
|
23
|
|
>>> v.y
|
|
23
|
|
|
|
'''
|
|
|
|
def __getslice__(self, i, j):
|
|
try:
|
|
# use the list __getslice__ method and convert
|
|
# result to vector
|
|
return Vector(super(Vector, self).__getslice__(i, j))
|
|
except Exception:
|
|
raise TypeError('vector::FAILURE in __getslice__')
|
|
|
|
def __add__(self, val):
|
|
return Vector(list(map(lambda x, y: x + y, self, val)))
|
|
|
|
def __iadd__(self, val):
|
|
if type(val) in (int, float):
|
|
self.x += val
|
|
self.y += val
|
|
else:
|
|
self.x += val.x
|
|
self.y += val.y
|
|
return self
|
|
|
|
def __neg__(self):
|
|
return Vector([-x for x in self])
|
|
|
|
def __sub__(self, val):
|
|
return Vector(list(map(lambda x, y: x - y, self, val)))
|
|
|
|
def __isub__(self, val):
|
|
if type(val) in (int, float):
|
|
self.x -= val
|
|
self.y -= val
|
|
else:
|
|
self.x -= val.x
|
|
self.y -= val.y
|
|
return self
|
|
|
|
def __mul__(self, val):
|
|
try:
|
|
return Vector(list(map(lambda x, y: x * y, self, val)))
|
|
except Exception:
|
|
return Vector([x * val for x in self])
|
|
|
|
def __imul__(self, val):
|
|
if type(val) in (int, float):
|
|
self.x *= val
|
|
self.y *= val
|
|
else:
|
|
self.x *= val.x
|
|
self.y *= val.y
|
|
return self
|
|
|
|
def __rmul__(self, val):
|
|
return (self * val)
|
|
|
|
def __truediv__(self, val):
|
|
try:
|
|
return Vector(list(map(lambda x, y: x / y, self, val)))
|
|
except Exception:
|
|
return Vector([x / val for x in self])
|
|
|
|
def __div__(self, val):
|
|
try:
|
|
return Vector(list(map(lambda x, y: x / y, self, val)))
|
|
except Exception:
|
|
return Vector([x / val for x in self])
|
|
|
|
def __rtruediv__(self, val):
|
|
try:
|
|
return Vector(*val) / self
|
|
except Exception:
|
|
return Vector(val, val) / self
|
|
|
|
def __rdiv__(self, val):
|
|
try:
|
|
return Vector(*val) / self
|
|
except Exception:
|
|
return Vector(val, val) / self
|
|
|
|
def __idiv__(self, val):
|
|
if type(val) in (int, float):
|
|
self.x /= val
|
|
self.y /= val
|
|
else:
|
|
self.x /= val.x
|
|
self.y /= val.y
|
|
return self
|
|
|
|
def length(self):
|
|
'''Returns the length of a vector.
|
|
|
|
>>> Vector(10, 10).length()
|
|
14.142135623730951
|
|
>>> pos = (10, 10)
|
|
>>> Vector(pos).length()
|
|
14.142135623730951
|
|
|
|
'''
|
|
return math.sqrt(self[0] ** 2 + self[1] ** 2)
|
|
|
|
def length2(self):
|
|
'''Returns the length of a vector squared.
|
|
|
|
>>> Vector(10, 10).length2()
|
|
200
|
|
>>> pos = (10, 10)
|
|
>>> Vector(pos).length2()
|
|
200
|
|
|
|
'''
|
|
return self[0] ** 2 + self[1] ** 2
|
|
|
|
def distance(self, to):
|
|
'''Returns the distance between two points.
|
|
|
|
>>> Vector(10, 10).distance((5, 10))
|
|
5.
|
|
>>> a = (90, 33)
|
|
>>> b = (76, 34)
|
|
>>> Vector(a).distance(b)
|
|
14.035668847618199
|
|
|
|
'''
|
|
return math.sqrt((self[0] - to[0]) ** 2 + (self[1] - to[1]) ** 2)
|
|
|
|
def distance2(self, to):
|
|
'''Returns the distance between two points squared.
|
|
|
|
>>> Vector(10, 10).distance2((5, 10))
|
|
25
|
|
|
|
'''
|
|
return (self[0] - to[0]) ** 2 + (self[1] - to[1]) ** 2
|
|
|
|
def normalize(self):
|
|
'''Returns a new vector that has the same direction as vec,
|
|
but has a length of one.
|
|
|
|
>>> v = Vector(88, 33).normalize()
|
|
>>> v
|
|
[0.93632917756904444, 0.3511234415883917]
|
|
>>> v.length()
|
|
1.0
|
|
|
|
'''
|
|
if self[0] == 0. and self[1] == 0.:
|
|
return Vector(0., 0.)
|
|
return self / self.length()
|
|
|
|
def dot(self, a):
|
|
'''Computes the dot product of a and b.
|
|
|
|
>>> Vector(2, 4).dot((2, 2))
|
|
12
|
|
|
|
'''
|
|
return self[0] * a[0] + self[1] * a[1]
|
|
|
|
def angle(self, a):
|
|
'''Computes the angle between a and b, and returns the angle in
|
|
degrees.
|
|
|
|
>>> Vector(100, 0).angle((0, 100))
|
|
-90.0
|
|
>>> Vector(87, 23).angle((-77, 10))
|
|
-157.7920283010705
|
|
|
|
'''
|
|
angle = -(180 / math.pi) * math.atan2(
|
|
self[0] * a[1] - self[1] * a[0],
|
|
self[0] * a[0] + self[1] * a[1])
|
|
return angle
|
|
|
|
def rotate(self, angle):
|
|
'''Rotate the vector with an angle in degrees.
|
|
|
|
>>> v = Vector(100, 0)
|
|
>>> v.rotate(45)
|
|
[70.71067811865476, 70.71067811865474]
|
|
|
|
'''
|
|
angle = math.radians(angle)
|
|
return Vector(
|
|
(self[0] * math.cos(angle)) - (self[1] * math.sin(angle)),
|
|
(self[1] * math.cos(angle)) + (self[0] * math.sin(angle)))
|
|
|
|
@staticmethod
|
|
def line_intersection(v1, v2, v3, v4):
|
|
'''
|
|
Finds the intersection point between the lines (1)v1->v2 and (2)v3->v4
|
|
and returns it as a vector object.
|
|
|
|
>>> a = (98, 28)
|
|
>>> b = (72, 33)
|
|
>>> c = (10, -5)
|
|
>>> d = (20, 88)
|
|
>>> Vector.line_intersection(a, b, c, d)
|
|
[15.25931928687196, 43.911669367909241]
|
|
|
|
.. warning::
|
|
|
|
This is a line intersection method, not a segment intersection.
|
|
|
|
For math see: http://en.wikipedia.org/wiki/Line-line_intersection
|
|
'''
|
|
# linear algebar sucks...seriously!!
|
|
x1, x2, x3, x4 = float(v1[0]), float(v2[0]), float(v3[0]), float(v4[0])
|
|
y1, y2, y3, y4 = float(v1[1]), float(v2[1]), float(v3[1]), float(v4[1])
|
|
|
|
u = (x1 * y2 - y1 * x2)
|
|
v = (x3 * y4 - y3 * x4)
|
|
denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
|
|
if denom == 0:
|
|
return None
|
|
|
|
px = (u * (x3 - x4) - (x1 - x2) * v) / denom
|
|
py = (u * (y3 - y4) - (y1 - y2) * v) / denom
|
|
|
|
return Vector(px, py)
|
|
|
|
@staticmethod
|
|
def segment_intersection(v1, v2, v3, v4):
|
|
'''
|
|
Finds the intersection point between segments (1)v1->v2 and (2)v3->v4
|
|
and returns it as a vector object.
|
|
|
|
>>> a = (98, 28)
|
|
>>> b = (72, 33)
|
|
>>> c = (10, -5)
|
|
>>> d = (20, 88)
|
|
>>> Vector.segment_intersection(a, b, c, d)
|
|
None
|
|
|
|
>>> a = (0, 0)
|
|
>>> b = (10, 10)
|
|
>>> c = (0, 10)
|
|
>>> d = (10, 0)
|
|
>>> Vector.segment_intersection(a, b, c, d)
|
|
[5, 5]
|
|
'''
|
|
|
|
# Yaaay! I love linear algebra applied within the realms of geometry.
|
|
x1, x2, x3, x4 = float(v1[0]), float(v2[0]), float(v3[0]), float(v4[0])
|
|
y1, y2, y3, y4 = float(v1[1]), float(v2[1]), float(v3[1]), float(v4[1])
|
|
|
|
# This is mostly the same as the line_intersection
|
|
u = (x1 * y2 - y1 * x2)
|
|
v = (x3 * y4 - y3 * x4)
|
|
denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
|
|
if denom == 0:
|
|
return None
|
|
|
|
px = (u * (x3 - x4) - (x1 - x2) * v) / denom
|
|
py = (u * (y3 - y4) - (y1 - y2) * v) / denom
|
|
# Here are the new bits
|
|
c1 = (x1 <= px <= x2) or (x2 <= px <= x1) or (x1 == x2)
|
|
c2 = (y1 <= py <= y2) or (y2 <= py <= y1) or (y1 == y2)
|
|
c3 = (x3 <= px <= x4) or (x4 <= px <= x3) or (x3 == x4)
|
|
c4 = (y3 <= py <= y4) or (y4 <= py <= y3) or (y3 == y4)
|
|
|
|
if (c1 and c2) and (c3 and c4):
|
|
return Vector(px, py)
|
|
else:
|
|
return None
|
|
|
|
@staticmethod
|
|
def in_bbox(point, a, b):
|
|
'''Return True if `point` is in the bounding box defined by `a`
|
|
and `b`.
|
|
|
|
>>> bmin = (0, 0)
|
|
>>> bmax = (100, 100)
|
|
>>> Vector.in_bbox((50, 50), bmin, bmax)
|
|
True
|
|
>>> Vector.in_bbox((647, -10), bmin, bmax)
|
|
False
|
|
|
|
'''
|
|
return ((point[0] <= a[0] and point[0] >= b[0] or
|
|
point[0] <= b[0] and point[0] >= a[0]) and
|
|
(point[1] <= a[1] and point[1] >= b[1] or
|
|
point[1] <= b[1] and point[1] >= a[1]))
|