1import math
2
3class Point(object):
4  """A representation of a point within the Beziers world.
5
6  Here are some things you can do with points. You can interpret
7  them as vectors, and add them together::
8
9    >>> a = Point(5,5)
10    >>> b = Point(10,10)
11    >>> a + b
12    <15.0,15.0>
13
14  You can multiply them by a scalar to scale them::
15
16    >>> a * 2
17    <10.0,10.0>
18
19  You can adjust them::
20
21    >>> a += b
22    >>> a
23    <15.0,15.0>
24
25  If you're using Python 3, you can abuse operator overloading
26  and compute the dot product of two vectors:
27
28    >>> a = Point(5,5)
29    >>> b = Point(10,10)
30    >>> a @ b
31    100.0
32
33"""
34
35  def __init__(self, x,y):
36    self.x = float(x)
37    self.y = float(y)
38
39  def __repr__(self):
40    return "<%s,%s>" % (self.x,self.y)
41
42  @classmethod
43  def fromRepr(klass,text):
44    import re
45    p = re.compile("^<([^,]+),([^>]+)>$")
46    m = p.match(text)
47    return klass(m.group(1), m.group(2))
48
49  def __eq__(self, other):
50    def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
51      return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
52    return isclose(self.x, other.x) and isclose(self.y, other.y)
53
54  def __hash__(self):
55    return  hash(self.x) << 32 ^ hash(self.y)
56
57  def __mul__(self, other):
58    """Multiply a point by a scalar."""
59    return Point(self.x * other, self.y * other)
60
61  def __div__(self, other):
62    return Point(self.x / other, self.y / other)
63
64  def __truediv__(self, other):
65    return Point(self.x / other, self.y / other)
66
67  def __add__(self, other):
68    return Point(self.x + other.x, self.y + other.y)
69
70  def __sub__(self, other):
71    return Point(self.x - other.x, self.y - other.y)
72
73  def __iadd__(self, other):
74    self.x += other.x
75    self.y += other.y
76    return self
77
78  def __isub__(self, other):
79    self.x -= other.x
80    self.y -= other.y
81    return self
82
83  def __matmul__(self,other): # Dot product. Abusing overloading. Sue me.
84    return self.dot(other)
85
86  def dot(self, other):
87    return self.x * other.x + self.y * other.y
88
89  def clone(self):
90    """Clone a point, returning a new object with the same co-ordinates."""
91    return Point(self.x,self.y)
92
93  def rounded(self):
94    """Return a point with the co-ordinates truncated to integers"""
95    return Point(int(self.x),int(self.y))
96
97  def lerp(self, other, t):
98    """Interpolate between two points, at time t."""
99    return self * (1-t) + other * (t)
100
101  @property
102  def squareMagnitude(self):
103    """Interpreting this point as a vector, returns the squared magnitude (Euclidean length) of the vector."""
104    return self.x*self.x + self.y*self.y
105
106  @property
107  def magnitude(self):
108    """Interpreting this point as a vector, returns the magnitude (Euclidean length) of the vector."""
109    return math.sqrt(self.squareMagnitude)
110
111  def toUnitVector(self):
112    """Divides this point by its magnitude, returning a vector of length 1."""
113    mag = self.magnitude
114    if mag == 0.0: mag = 1.0
115    return Point(self.x/mag, self.y/mag)
116
117  @property
118  def angle(self):
119    """Interpreting this point as a vector, returns the angle in radians of the vector."""
120    return math.atan2(self.y,self.x)
121
122  @property
123  def slope(self):
124    """Returns slope y/x"""
125    if self.x == 0: return 0
126    return self.y / self.x
127
128  @classmethod
129  def fromAngle(self,angle):
130    """Given an angle in radians, return a unit vector representing that angle."""
131    return Point(math.cos(angle), math.sin(angle)).toUnitVector()
132
133  def rotated(self,around,by):
134    """Return a new point found by rotating this point around another point, by an angle given in radians."""
135    delta = around - self
136    oldangle = delta.angle
137    newangle = oldangle + by
138    unitvector = Point.fromAngle(newangle)
139    new = around - unitvector * delta.magnitude
140    return new
141
142  def rotate(self,around,by):
143    """Mutate this point by rotating it around another point, by an angle given in radians."""
144    new = self.rotated(around, by)
145    self.x = new.x
146    self.y = new.y
147
148  def squareDistanceFrom(self,other):
149    """Returns the squared Euclidean distance between this point and another."""
150    return (self.x - other.x) * (self.x - other.x) + (self.y - other.y) * (self.y - other.y)
151
152  def distanceFrom(self,other):
153    """Returns the Euclidean distance between this point and another."""
154    return math.sqrt(self.squareDistanceFrom(other))
155
156  def transformed(self, transformation):
157    m = transformation.matrix
158    x, y = self.x, self.y
159    a1, a2, b1 = m[0]
160    a3, a4, b2 = m[1]
161    xPrime = a1 * x + a2 * y + b1
162    yPrime = a3 * x + a4 * y + b2
163    return Point(xPrime, yPrime)
164
165  def transform(self, transformation):
166    new = self.transformed(transformation)
167    self.x = new.x
168    self.y = new.y
169