test-kivy-app/kivy_venv/lib/python3.11/site-packages/kivy/vector.py
2024-09-15 15:12:16 +03:00

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]))