e2D 1.4.11__py3-none-any.whl → 1.4.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- e2D/__init__.py +355 -1030
- e2D/__init__.pyi +1176 -0
- e2D/envs.py +95 -146
- e2D/plots.py +145 -101
- e2D/utils.py +169 -69
- e2D/winrec.py +1 -5
- {e2D-1.4.11.dist-info → e2D-1.4.12.dist-info}/METADATA +2 -2
- e2D-1.4.12.dist-info/RECORD +12 -0
- {e2D-1.4.11.dist-info → e2D-1.4.12.dist-info}/WHEEL +1 -1
- e2D-1.4.11.dist-info/RECORD +0 -11
- {e2D-1.4.11.dist-info → e2D-1.4.12.dist-info}/LICENSE +0 -0
- {e2D-1.4.11.dist-info → e2D-1.4.12.dist-info}/top_level.txt +0 -0
e2D/__init__.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import numpy as _np
|
|
4
3
|
import math as _mt
|
|
5
4
|
import random as _rnd
|
|
6
5
|
|
|
@@ -9,515 +8,93 @@ HALF_PI = PI/2
|
|
|
9
8
|
QUARTER_PI = PI/4
|
|
10
9
|
DOUBLE_PI = PI*2
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
# """([\s\S]*?)"""
|
|
14
|
-
|
|
15
|
-
#
|
|
11
|
+
sign = lambda val: -1 if val < 0 else (1 if val > 0 else 0)
|
|
16
12
|
|
|
17
13
|
class Vector2D:
|
|
18
|
-
round_values_on_print
|
|
19
|
-
def __init__(self
|
|
20
|
-
"""
|
|
21
|
-
# Initialize a 2D vector with the specified x and y components.
|
|
22
|
-
|
|
23
|
-
## Parameters:
|
|
24
|
-
x (int | float, optional): The x-component of the vector. Default is 0.
|
|
25
|
-
y (int | float, optional): The y-component of the vector. Default is 0.
|
|
26
|
-
|
|
27
|
-
## Example:
|
|
28
|
-
vector1 = Vector2D() # Creates a vector with x=0 and y=0
|
|
29
|
-
vector2 = Vector2D(3, -2.5) # Creates a vector with x=3 and y=-2.5
|
|
30
|
-
|
|
31
|
-
## Explanation:
|
|
32
|
-
This constructor initializes a 2D vector with the specified x and y components.
|
|
33
|
-
|
|
34
|
-
If no arguments are provided, the default values for x and y are both set to 0.
|
|
35
|
-
|
|
36
|
-
The x and y components can be integers or floating-point numbers.
|
|
37
|
-
|
|
38
|
-
Example usage is shown in the "Example" section above.
|
|
39
|
-
"""
|
|
14
|
+
round_values_on_print = 2
|
|
15
|
+
def __init__(self, x=.0, y=.0) -> None:
|
|
40
16
|
self.x = x
|
|
41
17
|
self.y = y
|
|
42
|
-
|
|
43
|
-
def set(self:"Vector2D", x:int|float=0, y:int|float=0) -> None:
|
|
44
|
-
"""
|
|
45
|
-
# Change the components of the Vector2D other without creating a new one.
|
|
46
|
-
|
|
47
|
-
## Parameters:
|
|
48
|
-
x (int | float, optional): The new x-component to set. Default is 0.
|
|
49
|
-
y (int | float, optional): The new y-component to set. Default is 0.
|
|
50
|
-
|
|
51
|
-
## Example:
|
|
52
|
-
vector = Vector2D(1, 2)
|
|
53
|
-
vector.set(3, -4)
|
|
54
|
-
print(vector.x, vector.y) # Output: 3, -4
|
|
55
|
-
|
|
56
|
-
## Explanation:
|
|
57
|
-
The method updates the x and y components of the Vector2D other to the specified values.
|
|
58
|
-
|
|
59
|
-
If no arguments are provided, the default values for x and y are both set to 0.
|
|
60
|
-
|
|
61
|
-
The x and y components can be integers or floating-point numbers.
|
|
62
|
-
|
|
63
|
-
The method does not return any value, but it modifies the Vector2D other in place.
|
|
64
|
-
|
|
65
|
-
Example usage is shown in the "Example" section above.
|
|
66
|
-
"""
|
|
67
|
-
self.x = x
|
|
68
|
-
self.y = y
|
|
69
|
-
|
|
70
|
-
def distance_to(self:"Vector2D", other:"float|int|Vector2D|list|tuple", sqrd:bool=True) -> int|float:
|
|
71
|
-
"""
|
|
72
|
-
# Calculate the distance between the current Vector2D other and another other.
|
|
73
|
-
|
|
74
|
-
## Parameters:
|
|
75
|
-
other (float or int or Vector2D or list|tuple): The other other to which the distance is calculated.
|
|
76
|
-
squared (bool, optional): If True, return the squared distance. If False, return the actual distance.
|
|
77
|
-
Default is True.
|
|
78
|
-
|
|
79
|
-
## Returns:
|
|
80
|
-
int|float: The squared distance between the current Vector2D other and the other other if `squared` is True,
|
|
81
|
-
otherwise the actual distance.
|
|
82
|
-
|
|
83
|
-
## Example:
|
|
84
|
-
point1 = Vector2D(0, 0)
|
|
85
18
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
squared_distance = point1.distance_to(point2)
|
|
89
|
-
|
|
90
|
-
print(f"Squared Distance: {squared_distance}")
|
|
91
|
-
|
|
92
|
-
distance = point1.distance_to(point2, squared=False)
|
|
93
|
-
|
|
94
|
-
print(f"Actual Distance: {distance}")
|
|
95
|
-
|
|
96
|
-
This will calculate the squared and actual distances between the two points.
|
|
97
|
-
|
|
98
|
-
## Explanation:
|
|
99
|
-
The function calculates the squared distance between the current Vector2D other (self) and another other
|
|
100
|
-
(other) using the formula: (self.x - other.x)**2 + (self.y - other.y)**2.
|
|
101
|
-
|
|
102
|
-
The result is returned as the squared distance if `squared` is True, or as the actual distance if `squared` is False.
|
|
103
|
-
"""
|
|
104
|
-
other = self.__normalize__(other)
|
|
19
|
+
def distance_to(self, other, sqrd=True) -> int|float:
|
|
105
20
|
d = (self.x - other.x)**2 + (self.y - other.y)**2
|
|
106
21
|
return (d**(1/2) if sqrd else d)
|
|
107
22
|
|
|
108
|
-
def
|
|
109
|
-
return _mt.atan2(self.y, self.x)
|
|
110
|
-
|
|
111
|
-
def angle_to(self:"Vector2D", other:"float|int|Vector2D|list|tuple") -> int|float:
|
|
112
|
-
"""
|
|
113
|
-
# Calculate the angle between the current Vector2D other and another other.
|
|
114
|
-
|
|
115
|
-
## Parameters:
|
|
116
|
-
other (float or int or Vector2D or list|tuple): The other other to which the angle is calculated.
|
|
117
|
-
|
|
118
|
-
## Returns:
|
|
119
|
-
int|float: The angle in radians between the current Vector2D other and the other other.
|
|
120
|
-
|
|
121
|
-
## Example:
|
|
122
|
-
point1 = Vector2D(0, 0)
|
|
123
|
-
|
|
124
|
-
point2 = Vector2D(1, 1)
|
|
125
|
-
|
|
126
|
-
angle = point1.angle_to(point2)
|
|
127
|
-
|
|
128
|
-
print(f"Angle in radians: {angle}")
|
|
129
|
-
|
|
130
|
-
This will calculate the angle in radians between the two points.
|
|
131
|
-
|
|
132
|
-
## Explanation:
|
|
133
|
-
The function calculates the angle in radians between the current Vector2D other (self) and another other
|
|
134
|
-
(other) using the `atan2` function from the `math` module.
|
|
135
|
-
|
|
136
|
-
The result is returned as the angle in radians.
|
|
137
|
-
"""
|
|
138
|
-
other = self.__normalize__(other)
|
|
23
|
+
def angle_to(self, other) -> int|float:
|
|
139
24
|
return _mt.atan2(other.y - self.y, other.x - self.x)
|
|
140
25
|
|
|
141
|
-
def
|
|
142
|
-
|
|
143
|
-
# Calculate a new Vector2D point from the current point based on an angle in degs and a radius.
|
|
144
|
-
|
|
145
|
-
## Parameters:
|
|
146
|
-
rad (int|float): The angle in degs.
|
|
147
|
-
radius (int|float): The distance from the current point.
|
|
148
|
-
|
|
149
|
-
## Returns:
|
|
150
|
-
Vector2D: A new Vector2D point calculated from the current point.
|
|
26
|
+
def point_from_angle_and_radius(self, rad, radius) -> "Vector2D":
|
|
27
|
+
return Vector2D(radius * _mt.cos(rad) + self.x, radius * _mt.sin(rad) + self.y)
|
|
151
28
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
angle = 45
|
|
156
|
-
|
|
157
|
-
distance = 5
|
|
158
|
-
|
|
159
|
-
new_point = point1.point_from_degs(angle, distance)
|
|
160
|
-
|
|
161
|
-
print(new_point.x, new_point.y)
|
|
162
|
-
|
|
163
|
-
This will calculate a new point 5 units away from point1 at a 45-degree angle.
|
|
164
|
-
|
|
165
|
-
## Explanation:
|
|
166
|
-
The function calculates a new Vector2D point based on an angle in degs (degs) and a distance (radius)
|
|
167
|
-
from the current Vector2D point.
|
|
168
|
-
|
|
169
|
-
It computes the new x and y coordinates of the point using the trigonometric functions `cos` and `sin`
|
|
170
|
-
to determine the horizontal and vertical components of the new point.
|
|
171
|
-
|
|
172
|
-
The result is returned as a new Vector2D point with the calculated coordinates.
|
|
173
|
-
"""
|
|
174
|
-
x = radius * _mt.cos(_mt.radians(degs)) + self.x
|
|
175
|
-
y = radius * _mt.sin(_mt.radians(degs)) + self.y
|
|
176
|
-
return Vector2D(x, y)
|
|
177
|
-
|
|
178
|
-
def point_from_rads(self:"Vector2D", rad:int|float, radius:int|float) -> "Vector2D":
|
|
179
|
-
"""
|
|
180
|
-
# Calculate a new Vector2D point from the current point based on an angle in radians and a radius.
|
|
181
|
-
|
|
182
|
-
## Parameters:
|
|
183
|
-
rad (int|float): The angle in radians.
|
|
184
|
-
radius (int|float): The distance from the current point.
|
|
185
|
-
|
|
186
|
-
## Returns:
|
|
187
|
-
Vector2D: A new Vector2D point calculated from the current point.
|
|
188
|
-
|
|
189
|
-
## Example:
|
|
190
|
-
point1 = Vector2D(0, 0)
|
|
191
|
-
|
|
192
|
-
angle = 45
|
|
193
|
-
|
|
194
|
-
distance = 5
|
|
195
|
-
|
|
196
|
-
new_point = point1.point_from_degs(_mt.radians(angle), distance)
|
|
197
|
-
|
|
198
|
-
print(new_point.x, new_point.y)
|
|
199
|
-
|
|
200
|
-
This will calculate a new point 5 units away from point1 at a 45-degree angle.
|
|
201
|
-
|
|
202
|
-
## Explanation:
|
|
203
|
-
The function calculates a new Vector2D point based on an angle in radians (rad) and a distance (radius)
|
|
204
|
-
from the current Vector2D point.
|
|
205
|
-
|
|
206
|
-
It computes the new x and y coordinates of the point using the trigonometric functions `cos` and `sin`
|
|
207
|
-
to determine the horizontal and vertical components of the new point.
|
|
208
|
-
|
|
209
|
-
The result is returned as a new Vector2D point with the calculated coordinates.
|
|
210
|
-
"""
|
|
211
|
-
x = radius * _mt.cos(rad) + self.x
|
|
212
|
-
y = radius * _mt.sin(rad) + self.y
|
|
213
|
-
return Vector2D(x, y)
|
|
214
|
-
|
|
215
|
-
def copy(self:"Vector2D") -> "Vector2D":
|
|
216
|
-
"""
|
|
217
|
-
# Create a copy of the current Vector2D other.
|
|
218
|
-
|
|
219
|
-
## Returns:
|
|
220
|
-
Vector2D: A new Vector2D other with the same x and y coordinates as the current other.
|
|
221
|
-
|
|
222
|
-
## Example:
|
|
223
|
-
point1 = Vector2D(1, 2)
|
|
224
|
-
|
|
225
|
-
point2 = point1.copy()
|
|
226
|
-
|
|
227
|
-
print(point2.x, point2.y)
|
|
228
|
-
|
|
229
|
-
This will print the x and y coordinates of the copied Vector2D other (1, 2).
|
|
29
|
+
@property
|
|
30
|
+
def angle(self) -> int|float:
|
|
31
|
+
return _mt.atan2(self.y, self.x)
|
|
230
32
|
|
|
231
|
-
|
|
232
|
-
|
|
33
|
+
@angle.setter
|
|
34
|
+
def angle(self, argv) -> None:
|
|
35
|
+
print(argv)
|
|
233
36
|
|
|
234
|
-
|
|
235
|
-
|
|
37
|
+
@property
|
|
38
|
+
def copy(self) -> "Vector2D":
|
|
236
39
|
return Vector2D(self.x, self.y)
|
|
237
40
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
result1 = vector1.absolute_round(0.5)
|
|
252
|
-
|
|
253
|
-
print(result1.x, result1.y)
|
|
254
|
-
|
|
255
|
-
vector2 = Vector2D(-2.8, 1.1)
|
|
256
|
-
|
|
257
|
-
result2 = vector2.absolute_round()
|
|
258
|
-
|
|
259
|
-
print(result2.x, result2.y)
|
|
260
|
-
|
|
261
|
-
## Explanation:
|
|
262
|
-
The function performs an "absolute round" operation on the Vector2D other.
|
|
263
|
-
|
|
264
|
-
The "absolute round" operation involves taking the absolute values of both the x and y components of the Vector2D other,
|
|
265
|
-
and then scaling the resulting vector by the provided numeric value (n).
|
|
266
|
-
|
|
267
|
-
The default value of n is 1, which means the "absolute rounded" vector will have the same magnitude as the original vector.
|
|
268
|
-
|
|
269
|
-
If the provided numeric value (n) is 0, the function returns a Vector2D other with zeros for both components.
|
|
270
|
-
|
|
271
|
-
If the provided numeric value (n) is negative, the resulting "absolute rounded" vector will point in the opposite direction
|
|
272
|
-
as the original vector but will have the same magnitude.
|
|
273
|
-
|
|
274
|
-
Note: The "absolute round" operation does not perform standard mathematical rounding; instead, it ensures the resulting
|
|
275
|
-
vector points in the same direction as the original vector but has non-negative components.
|
|
276
|
-
"""
|
|
277
|
-
return self.no_zero_div_error(abs(self), "zero")
|
|
41
|
+
@property
|
|
42
|
+
def sign(self) -> "Vector2D":
|
|
43
|
+
return Vector2D(sign(self.x), sign(self.y))
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def normalize(self) -> "Vector2D":
|
|
47
|
+
if (mag:=self.length) == 0:
|
|
48
|
+
return self.copy
|
|
49
|
+
return Vector2D(self.x / mag, self.y / mag)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def length(self) -> float:
|
|
53
|
+
return (self.x ** 2 + self.y ** 2) ** .5
|
|
278
54
|
|
|
279
|
-
def floor(self
|
|
55
|
+
def floor(self, n=1) -> "Vector2D":
|
|
280
56
|
return self.__floor__(n)
|
|
281
57
|
|
|
282
|
-
def ceil(self
|
|
58
|
+
def ceil(self, n=1) -> "Vector2D":
|
|
283
59
|
return self.__ceil__(n)
|
|
284
60
|
|
|
285
|
-
def round(self
|
|
61
|
+
def round(self, n=1) -> "Vector2D":
|
|
286
62
|
return self.__round__(n)
|
|
287
63
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
end (int
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
Vector2D: A new random Vector2D point within the specified range.
|
|
302
|
-
|
|
303
|
-
## Example:
|
|
304
|
-
random_point = randomize(Vector2D(10, 20), Vector2D(50, 70))
|
|
305
|
-
|
|
306
|
-
print(random_point.x, random_point.y)
|
|
307
|
-
|
|
308
|
-
This will print a random point between (10, 20) and (50, 70).
|
|
309
|
-
|
|
310
|
-
## Explanation:
|
|
311
|
-
The function generates a random Vector2D point within the specified range defined by `start` and `end`.
|
|
312
|
-
|
|
313
|
-
If `start` and `end` are numeric values (int or float), both x and y coordinates will have the same value.
|
|
314
|
-
|
|
315
|
-
If `start` and `end` are None, the default range is assumed to be (0, 0) to (1, 1).
|
|
316
|
-
|
|
317
|
-
The function first checks if `start` and `end` are Vector2D others. If not, it creates new Vector2D others
|
|
318
|
-
based on the numeric values provided or the default values.
|
|
319
|
-
|
|
320
|
-
It then generates random x and y coordinates in the range [0, 1) using the `random()` function from the `random` module.
|
|
321
|
-
These random values are then scaled by (end - start) and added to the start point to obtain the final random Vector2D point.
|
|
322
|
-
"""
|
|
323
|
-
if not any(isinstance(start, cls) for cls in {Vector2D, V2}):
|
|
324
|
-
if type(start) in int|float: start = Vector2D(start, start) #type: ignore
|
|
325
|
-
elif type(start) == None: start = Vector2D(0,0)
|
|
326
|
-
else: raise Exception(f"\nArg start must be in [Vector2D, int, float, tuple, list] not a [{type(start)}]\n")
|
|
327
|
-
if not any(isinstance(end, cls) for cls in {Vector2D, V2}):
|
|
328
|
-
if type(end) in int|float: end = Vector2D(end, end) #type: ignore
|
|
329
|
-
elif type(end) == None: end = Vector2D(1,1)
|
|
330
|
-
else: raise Exception(f"\nArg end must be in [Vector2D, int, float, tuple, list] not a [{type(end)}]\n")
|
|
331
|
-
return start + Vector2D(_rnd.random(), _rnd.random()) * (end - start) #type: ignore
|
|
64
|
+
@classmethod
|
|
65
|
+
def randomize(cls, start, end) -> "Vector2D":
|
|
66
|
+
if not isinstance(start, Vector2D):
|
|
67
|
+
if isinstance(start, (int, float)):
|
|
68
|
+
start = Vector2D(start, start)
|
|
69
|
+
else:
|
|
70
|
+
raise Exception(f"\nArg start must be in [Vector2D, int, float, tuple, list] not a [{type(start)}]\n")
|
|
71
|
+
if not isinstance(end, Vector2D):
|
|
72
|
+
if isinstance(end, (int, float)):
|
|
73
|
+
end = Vector2D(end, end)
|
|
74
|
+
else:
|
|
75
|
+
raise Exception(f"\nArg end must be in [Vector2D, int, float, tuple, list] not a [{type(end)}]\n")
|
|
76
|
+
return start + Vector2D(_rnd.random(), _rnd.random()) * (end - start)
|
|
332
77
|
|
|
333
|
-
def dot_product(self, other
|
|
334
|
-
other = self.__normalize__(other)
|
|
335
|
-
"""
|
|
336
|
-
# Calculate the dot product of the current vector with another vector.
|
|
337
|
-
|
|
338
|
-
## Parameters:
|
|
339
|
-
other (Vector2D): The other vector for the dot product calculation.
|
|
340
|
-
|
|
341
|
-
## Returns:
|
|
342
|
-
float: The dot product value.
|
|
343
|
-
|
|
344
|
-
## Example:
|
|
345
|
-
v1 = Vector2D(2, 3)
|
|
346
|
-
v2 = Vector2D(4, -1)
|
|
347
|
-
result = v1.dot_product(v2)
|
|
348
|
-
print(result) # Output: 5
|
|
349
|
-
|
|
350
|
-
## Explanation:
|
|
351
|
-
The dot product of two vectors (A and B) is given by the formula: dot_product = A.x * B.x + A.y * B.y
|
|
352
|
-
|
|
353
|
-
The method takes another vector (other) as input and returns the dot product value.
|
|
354
|
-
|
|
355
|
-
Example usage is shown in the "Example" section above.
|
|
356
|
-
"""
|
|
78
|
+
def dot_product(self, other) -> float:
|
|
357
79
|
return self.x * other.x + self.y * other.y
|
|
358
80
|
|
|
359
|
-
def
|
|
360
|
-
"""
|
|
361
|
-
# Vector Normalization
|
|
362
|
-
|
|
363
|
-
## Returns:
|
|
364
|
-
Vector2D: A new vector with the same direction as the current vector but with a magnitude of 1.
|
|
365
|
-
|
|
366
|
-
## Raises:
|
|
367
|
-
ValueError: If the magnitude of the current vector is zero (zero vector).
|
|
368
|
-
|
|
369
|
-
## Example:
|
|
370
|
-
v = Vector2D(3, 4)
|
|
371
|
-
normalized_v = v.normalize() # Normalize the vector (3, 4)
|
|
372
|
-
print(normalized_v) # Output: (0.6, 0.8)
|
|
373
|
-
|
|
374
|
-
## Explanation:
|
|
375
|
-
This method calculates the normalized version of the current vector, which means a new vector with the same direction as the original but with a magnitude of 1.
|
|
376
|
-
|
|
377
|
-
The method first calculates the magnitude of the current vector using the 'magnitude' method.
|
|
378
|
-
|
|
379
|
-
If the magnitude is zero (zero vector), a ValueError is raised, as normalization is not defined for zero vectors.
|
|
380
|
-
|
|
381
|
-
The normalized vector is obtained by dividing each component of the current vector by its magnitude.
|
|
382
|
-
|
|
383
|
-
The resulting normalized vector is returned.
|
|
384
|
-
|
|
385
|
-
Example usage is shown in the "Example" section above.
|
|
386
|
-
"""
|
|
387
|
-
mag = self.length()
|
|
388
|
-
if mag == 0:
|
|
389
|
-
return self
|
|
390
|
-
return Vector2D(self.x / mag, self.y / mag)
|
|
391
|
-
|
|
392
|
-
def projection(self, other:"float|int|Vector2D|list|tuple") -> "Vector2D":
|
|
393
|
-
"""
|
|
394
|
-
# Vector Projection
|
|
395
|
-
|
|
396
|
-
## Parameters:
|
|
397
|
-
other (float, int, Vector2D, V2, list, tuple): The vector onto which to project.
|
|
398
|
-
|
|
399
|
-
## Returns:
|
|
400
|
-
Vector2D or V2: The projection of the current vector onto the 'other' vector.
|
|
401
|
-
|
|
402
|
-
## Raises:
|
|
403
|
-
ValueError: If 'other' is a zero vector.
|
|
404
|
-
|
|
405
|
-
## Example:
|
|
406
|
-
v1 = Vector2D(3, 4)
|
|
407
|
-
v2 = Vector2D(1, 0)
|
|
408
|
-
projection_v = v1.projection(v2) # Calculate the projection of v1 onto v2
|
|
409
|
-
print(projection_v) # Output: (3.0, 0.0)
|
|
410
|
-
|
|
411
|
-
## Explanation:
|
|
412
|
-
This method calculates the projection of the current vector onto the 'other' vector.
|
|
413
|
-
The projection is a vector that represents the component of the current vector in the direction of the 'other' vector.
|
|
414
|
-
|
|
415
|
-
If 'other' is not a Vector2D instance, it will be converted to one using the '__normalize__' method.
|
|
416
|
-
The method first normalizes the 'other' vector using the '__normalize__' method of the vector.
|
|
417
|
-
|
|
418
|
-
Next, it calculates the dot product of the current vector and the normalized 'other' vector using the 'dot_product' method.
|
|
419
|
-
It also calculates the squared magnitude of the 'other' vector using the 'magnitude' method.
|
|
420
|
-
|
|
421
|
-
If the magnitude of 'other' is zero (a zero vector), a ValueError is raised, as projection is not defined for zero vectors.
|
|
422
|
-
|
|
423
|
-
The projection is then obtained by scaling the 'other' vector by the dot product divided by the squared magnitude.
|
|
424
|
-
|
|
425
|
-
The resulting projection vector is returned.
|
|
426
|
-
|
|
427
|
-
Example usage is shown in the "Example" section above.
|
|
428
|
-
"""
|
|
429
|
-
other = self.__normalize__(other)
|
|
81
|
+
def projection(self, other) -> "Vector2D":
|
|
430
82
|
dot_product = self.dot_product(other)
|
|
431
|
-
magnitude_product = other.length
|
|
83
|
+
magnitude_product = other.length ** 2
|
|
432
84
|
if magnitude_product == 0:
|
|
433
85
|
raise ValueError("Cannot calculate projection for zero vectors.")
|
|
434
86
|
return other * (dot_product / magnitude_product)
|
|
435
87
|
|
|
436
|
-
def reflection(self, normal
|
|
437
|
-
|
|
438
|
-
# Vector Reflection
|
|
439
|
-
|
|
440
|
-
## Parameters:
|
|
441
|
-
normal (float, int, Vector2D, V2, list, tuple): The normal vector representing the surface of reflection.
|
|
442
|
-
|
|
443
|
-
## Returns:
|
|
444
|
-
Vector2D or V2: The reflected vector.
|
|
445
|
-
|
|
446
|
-
## Example:
|
|
447
|
-
incident_vector = Vector2D(3, 4)
|
|
448
|
-
normal_vector = Vector2D(1, 0)
|
|
449
|
-
reflected_vector = incident_vector.reflection(normal_vector) # Calculate the reflection of the incident vector over the given normal
|
|
450
|
-
print(reflected_vector) # Output: (-3.0, 4.0)
|
|
451
|
-
|
|
452
|
-
## Explanation:
|
|
453
|
-
This method calculates the reflection of the current vector over the given normal vector.
|
|
454
|
-
The normal vector represents the surface of reflection, and it should be normalized (unit vector).
|
|
455
|
-
|
|
456
|
-
The method first normalizes the 'normal' vector using the '__normalize__' method of the vector.
|
|
457
|
-
Next, it calculates the projection of the current vector onto the 'normal' vector using the 'projection' method.
|
|
458
|
-
The reflected vector is obtained by subtracting twice the projection from the current vector.
|
|
459
|
-
|
|
460
|
-
The resulting reflected vector is returned.
|
|
461
|
-
|
|
462
|
-
Example usage is shown in the "Example" section above.
|
|
463
|
-
"""
|
|
464
|
-
normal = self.__normalize__(normal)
|
|
465
|
-
projection = self.projection(normal)
|
|
466
|
-
return self - projection * 2
|
|
88
|
+
def reflection(self, normal) -> "Vector2D":
|
|
89
|
+
return self - self.projection(normal) * 2
|
|
467
90
|
|
|
468
91
|
def cartesian_to_polar(self) -> tuple:
|
|
469
|
-
|
|
470
|
-
# Convert Cartesian Coordinates to Polar Coordinates
|
|
471
|
-
|
|
472
|
-
## Returns:
|
|
473
|
-
tuple: A tuple containing the radial distance (magnitude) 'r' and the angle 'theta' in radians.
|
|
474
|
-
|
|
475
|
-
## Example:
|
|
476
|
-
v = Vector2D(3, 4)
|
|
477
|
-
r, theta = v.cartesian_to_polar() # Convert Cartesian coordinates (3, 4) to polar
|
|
478
|
-
print(r, theta) # Output: (5.0, 0.9272952180016122)
|
|
479
|
-
|
|
480
|
-
## Explanation:
|
|
481
|
-
This method converts Cartesian coordinates (x, y) to polar coordinates (r, theta).
|
|
482
|
-
'r' is the radial distance (magnitude) from the origin to the point, and 'theta' is the angle
|
|
483
|
-
(in radians) measured from the positive x-axis to the point.
|
|
484
|
-
|
|
485
|
-
The method calculates the radial distance 'r' using the 'magnitude' method of the vector.
|
|
486
|
-
The angle 'theta' is calculated using the arctan2 function, which takes the y and x components of the vector.
|
|
487
|
-
|
|
488
|
-
The resulting 'r' and 'theta' are returned as a tuple.
|
|
489
|
-
|
|
490
|
-
Example usage is shown in the "Example" section above.
|
|
491
|
-
"""
|
|
492
|
-
r = self.length()
|
|
92
|
+
r = self.length
|
|
493
93
|
theta = _mt.atan2(self.y, self.x)
|
|
494
94
|
return r, theta
|
|
495
95
|
|
|
496
96
|
@classmethod
|
|
497
|
-
def polar_to_cartesian(cls, r
|
|
498
|
-
"""
|
|
499
|
-
# Convert Polar Coordinates to Cartesian Coordinates
|
|
500
|
-
|
|
501
|
-
## Parameters:
|
|
502
|
-
r (float or int): The radial distance (magnitude) from the origin to the point.
|
|
503
|
-
theta (float or int): The angle (in radians or degrees) measured from the positive x-axis to the point.
|
|
504
|
-
|
|
505
|
-
## Returns:
|
|
506
|
-
Vector2D or V2: A new vector representing the Cartesian coordinates (x, y) of the point.
|
|
507
|
-
|
|
508
|
-
## Example:
|
|
509
|
-
cartesian_point = Vector2D.polar_to_cartesian(5, math.pi/4) # Convert polar coordinates (r=5, theta=45 degrees) to Cartesian
|
|
510
|
-
print(cartesian_point) # Output: (3.5355339059327378, 3.5355339059327373)
|
|
511
|
-
|
|
512
|
-
## Explanation:
|
|
513
|
-
This class method converts polar coordinates (r, theta) to Cartesian coordinates (x, y).
|
|
514
|
-
'r' is the radial distance (magnitude) from the origin to the point, and 'theta' is the angle
|
|
515
|
-
(in radians or degrees) measured from the positive x-axis to the point.
|
|
516
|
-
|
|
517
|
-
The method calculates the x and y components using trigonometric functions (cosine and sine) based on 'r' and 'theta'.
|
|
518
|
-
|
|
519
|
-
Example usage is shown in the "Example" section above.
|
|
520
|
-
"""
|
|
97
|
+
def polar_to_cartesian(cls, r, theta) -> "Vector2D":
|
|
521
98
|
x = r * _mt.cos(theta)
|
|
522
99
|
y = r * _mt.sin(theta)
|
|
523
100
|
return cls(x, y)
|
|
@@ -526,150 +103,40 @@ class Vector2D:
|
|
|
526
103
|
return self.x + self.y * 1j
|
|
527
104
|
|
|
528
105
|
@classmethod
|
|
529
|
-
def complex_to_cartesian(cls, complex_n
|
|
106
|
+
def complex_to_cartesian(cls, complex_n) -> "Vector2D":
|
|
530
107
|
return cls(complex_n.real, complex_n.imag)
|
|
531
108
|
|
|
532
|
-
def
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
def lerp(self, other:"float|int|Vector2D|list|tuple", t: float) -> "Vector2D":
|
|
536
|
-
"""
|
|
537
|
-
# Linear Interpolation (LERP)
|
|
538
|
-
|
|
539
|
-
## Parameters:
|
|
540
|
-
other (float, int, Vector2D, V2, list, tuple): The vector to interpolate towards.
|
|
541
|
-
t (float): The interpolation parameter. Must be between 0 and 1.
|
|
542
|
-
|
|
543
|
-
## Returns:
|
|
544
|
-
Vector2D or V2: The result of the linear interpolation.
|
|
545
|
-
|
|
546
|
-
## Raises:
|
|
547
|
-
ValueError: If t is not within the range [0, 1].
|
|
548
|
-
|
|
549
|
-
## Example:
|
|
550
|
-
v1 = Vector2D(1, 2)
|
|
551
|
-
v2 = Vector2D(5, 7)
|
|
552
|
-
interpolated_v = v1.lerp(v2, 0.5) # Linearly interpolate between v1 and v2 with t = 0.5
|
|
553
|
-
print(interpolated_v) # Output: (3.0, 4.5)
|
|
554
|
-
|
|
555
|
-
## Explanation:
|
|
556
|
-
This method performs linear interpolation between the current vector and the 'other' vector.
|
|
557
|
-
The 't' parameter represents the interpolation parameter, which controls how much the interpolation
|
|
558
|
-
leans towards the 'other' vector. When 't' is 0, the result will be equal to the current vector (self).
|
|
559
|
-
When 't' is 1, the result will be equal to the 'other' vector. For intermediate values of 't', the
|
|
560
|
-
result will be a linear combination of the two vectors, smoothly transitioning between them.
|
|
561
|
-
|
|
562
|
-
If 'other' is not a Vector2D instance, it will be converted to one using the '__normalize__' method.
|
|
563
|
-
If 't' is not within the range [0, 1], a ValueError is raised.
|
|
564
|
-
|
|
565
|
-
Example usage is shown in the "Example" section above.
|
|
566
|
-
"""
|
|
567
|
-
other = self.__normalize__(other)
|
|
109
|
+
def lerp(self, other, t=.1) -> "Vector2D":
|
|
110
|
+
other = Vector2D.__normalize__(other)
|
|
568
111
|
if not 0 <= t <= 1:
|
|
569
112
|
raise ValueError("t must be between 0 and 1 for linear interpolation.")
|
|
570
113
|
return Vector2D(self.x + (other.x - self.x) * t, self.y + (other.y - self.y) * t)
|
|
571
114
|
|
|
572
|
-
def rotate(self, angle
|
|
573
|
-
|
|
574
|
-
# Rotate the vector by a given angle around the origin or a specified center.
|
|
575
|
-
|
|
576
|
-
## Parameters:
|
|
577
|
-
angle (int or float): The angle of rotation in radians or degrees, depending on the trigonometric functions used.
|
|
578
|
-
center (float, int, Vector2D, V2, list, tuple, or None): The center of rotation.
|
|
579
|
-
If None, the vector is rotated around the origin (0, 0).
|
|
580
|
-
|
|
581
|
-
## Returns:
|
|
582
|
-
Vector2D or V2: The rotated vector.
|
|
583
|
-
|
|
584
|
-
## Example:
|
|
585
|
-
v = Vector2D(3, 4)
|
|
586
|
-
rotated_v = v.rotate(math.pi / 4) # Rotate 45 degrees around the origin
|
|
587
|
-
print(rotated_v) # Output: (0.7071067811865476, 5.656854249492381)
|
|
588
|
-
|
|
589
|
-
center = Vector2D(1, 1)
|
|
590
|
-
rotated_v = v.rotate(math.pi / 4, center) # Rotate 45 degrees around the center (1, 1)
|
|
591
|
-
print(rotated_v) # Output: (1.7071067811865475, 2.656854249492381)
|
|
592
|
-
|
|
593
|
-
## Explanation:
|
|
594
|
-
This method rotates the vector by the specified angle around the given center.
|
|
595
|
-
If no center is provided, the vector is rotated around the origin (0, 0).
|
|
596
|
-
|
|
597
|
-
The method calculates the trigonometric functions (cosine and sine) of the angle to perform the rotation.
|
|
598
|
-
The translated vector is obtained by subtracting the center from the current vector.
|
|
599
|
-
The rotated vector is then obtained by applying the rotation transformation to the translated vector.
|
|
600
|
-
The center is added back to the rotated vector to obtain the final result.
|
|
601
|
-
|
|
602
|
-
Example usage is shown in the "Example" section above.
|
|
603
|
-
"""
|
|
604
|
-
if center is None: center = V2z
|
|
605
|
-
else: center = self.__normalize__(center)
|
|
115
|
+
def rotate(self, angle, center=None) -> "Vector2D":
|
|
116
|
+
if center is None: center = Vector2D.zero()
|
|
606
117
|
translated = self - center
|
|
607
118
|
cos_angle = _mt.cos(angle)
|
|
608
119
|
sin_angle = _mt.sin(angle)
|
|
609
120
|
return Vector2D(translated.x * cos_angle - translated.y * sin_angle, translated.x * sin_angle + translated.y * cos_angle) + center
|
|
610
121
|
|
|
611
|
-
def no_zero_div_error(self
|
|
612
|
-
|
|
613
|
-
# Handle division between the Vector2D other and a numeric value or another Vector2D other.
|
|
614
|
-
|
|
615
|
-
## Parameters:
|
|
616
|
-
n (int|float or Vector2D): The numeric value or Vector2D other for division.
|
|
617
|
-
error_mode (str, optional): The mode to handle division by zero scenarios.
|
|
618
|
-
- "zero" (default): Return a Vector2D other with zeros for both components.
|
|
619
|
-
- "null": Return a Vector2D other with the original x or y component if available,
|
|
620
|
-
otherwise, return NaN (Not a Number) for the component.
|
|
621
|
-
|
|
622
|
-
## Returns:
|
|
623
|
-
Vector2D: A new Vector2D other after division or handling division by zero scenarios.
|
|
624
|
-
|
|
625
|
-
## Example:
|
|
626
|
-
vector1 = Vector2D(3, 4)
|
|
627
|
-
|
|
628
|
-
result1 = vector1.no_zero_div_error(2)
|
|
629
|
-
|
|
630
|
-
print(result1.x, result1.y)
|
|
631
|
-
|
|
632
|
-
vector2 = Vector2D(5, 0)
|
|
633
|
-
|
|
634
|
-
result2 = vector1.no_zero_div_error(vector2, error_mode="null")
|
|
635
|
-
|
|
636
|
-
print(result2.x, result2.y)
|
|
637
|
-
|
|
638
|
-
## Explanation:
|
|
639
|
-
The function handles division between the Vector2D other and a numeric value or another Vector2D other.
|
|
640
|
-
|
|
641
|
-
If n is a numeric value (int or float):
|
|
642
|
-
- If n is zero, the function returns a Vector2D other with zeros for both components if error_mode is "zero".
|
|
643
|
-
- If error_mode is "null", the function returns a Vector2D other with the original x or y component if available,
|
|
644
|
-
otherwise, return NaN (Not a Number) for the component.
|
|
645
|
-
|
|
646
|
-
If n is a Vector2D other:
|
|
647
|
-
- If n's x or y component is zero, the function returns a Vector2D other with zeros for the corresponding component
|
|
648
|
-
if error_mode is "zero".
|
|
649
|
-
- If error_mode is "null", the function returns a Vector2D other with the original x or y component if available,
|
|
650
|
-
otherwise, return NaN (Not a Number) for the component.
|
|
651
|
-
|
|
652
|
-
If n is neither a numeric value nor a Vector2D other, the function raises an exception.
|
|
653
|
-
"""
|
|
654
|
-
if any(isinstance(n, cls) for cls in {int, float}):
|
|
122
|
+
def no_zero_div_error(self, n, error_mode="zero") -> "Vector2D":
|
|
123
|
+
if isinstance(n, (int, float)):
|
|
655
124
|
if n == 0:
|
|
656
|
-
return Vector2D(0 if error_mode ==
|
|
125
|
+
return Vector2D(0 if error_mode == "zero" else (self.x if error_mode == "null" else _mt.nan), 0 if error_mode == "zero" else (self.y if error_mode == "null" else _mt.nan))
|
|
657
126
|
else:
|
|
658
127
|
return self / n
|
|
659
|
-
elif
|
|
128
|
+
elif isinstance(n, Vector2D):
|
|
660
129
|
return Vector2D((0 if error_mode == "zero" else (self.x if error_mode == "null" else _mt.nan)) if n.x == 0 else self.x / n.x, (0 if error_mode == "zero" else (self.y if error_mode == "null" else _mt.nan)) if n.y == 0 else self.y / n.y) #type: ignore
|
|
661
130
|
else:
|
|
662
131
|
raise Exception(f"\nArg n must be in [Vector2D, int, float, tuple, list] not a [{type(n)}]\n")
|
|
663
132
|
|
|
664
|
-
def min(self
|
|
665
|
-
other = self.__normalize__(other)
|
|
133
|
+
def min(self, other) -> "Vector2D":
|
|
666
134
|
return Vector2D(min(self.x, other.x), min(self.y, other.y))
|
|
667
135
|
|
|
668
|
-
def max(self
|
|
669
|
-
other = self.__normalize__(other)
|
|
136
|
+
def max(self, other) -> "Vector2D":
|
|
670
137
|
return Vector2D(max(self.x, other.x), max(self.y, other.y))
|
|
671
138
|
|
|
672
|
-
def advanced_stringify(self
|
|
139
|
+
def advanced_stringify(self, precision=None, use_scientific_notation=False, return_as_list=False) -> str|list[str]:
|
|
673
140
|
precision = self.round_values_on_print if precision == None else precision
|
|
674
141
|
def optimize(value) -> str:
|
|
675
142
|
abs_value = abs(value)
|
|
@@ -680,433 +147,395 @@ class Vector2D:
|
|
|
680
147
|
else:
|
|
681
148
|
return f"{value:.{precision}e}"
|
|
682
149
|
if return_as_list:
|
|
683
|
-
return [optimize(self.x), optimize(self.y)] if use_scientific_notation else [f"{self.x:.{precision}f}", f"{self.y:.{precision}f}"]
|
|
150
|
+
return [f"{optimize(self.x)}", f"{optimize(self.y)}"] if use_scientific_notation else [f"{self.x:.{precision}f}", f"{self.y:.{precision}f}"]
|
|
684
151
|
return f"{optimize(self.x)}, {optimize(self.y)}" if use_scientific_notation else f"{self.x:.{precision}f}, {self.y:.{precision}f}"
|
|
685
152
|
|
|
686
|
-
def __str__(self
|
|
153
|
+
def __str__(self) -> str:
|
|
687
154
|
return f"{self.x:.{self.round_values_on_print}f}, {self.y:.{self.round_values_on_print}f}"
|
|
688
155
|
|
|
689
|
-
def __repr__(self
|
|
156
|
+
def __repr__(self) -> str:
|
|
690
157
|
return f"x:{self.x:.{self.round_values_on_print}f}\ty:{self.y:.{self.round_values_on_print}f}"
|
|
691
158
|
|
|
692
|
-
def __call__(self
|
|
693
|
-
return
|
|
159
|
+
def __call__(self) -> list:
|
|
160
|
+
return [self.x, self.y]
|
|
161
|
+
|
|
162
|
+
# fast operations Vector2D.operation(both,x,y)
|
|
163
|
+
def add(self, both=.0, x=.0, y=.0) -> Vector2D:
|
|
164
|
+
return Vector2D(self.x + (x + both), self.y + (y + both))
|
|
165
|
+
|
|
166
|
+
def sub(self, both=.0, x=.0, y=.0) -> Vector2D:
|
|
167
|
+
return Vector2D(self.x - (x + both), self.y - (y + both))
|
|
168
|
+
|
|
169
|
+
def mult(self, both=.0, x=.0, y=.0) -> Vector2D:
|
|
170
|
+
return Vector2D(self.x * (x + both), self.y * (y + both))
|
|
171
|
+
|
|
172
|
+
def pow(self, both=.0, x=.0, y=.0) -> Vector2D:
|
|
173
|
+
return Vector2D(self.x ** (x + both), self.y ** (y + both))
|
|
174
|
+
|
|
175
|
+
def mod(self, both=.0, x=.0, y=.0) -> Vector2D:
|
|
176
|
+
return Vector2D(self.x % (x + both), self.y % (y + both))
|
|
177
|
+
|
|
178
|
+
def div(self, both=.0, x=.0, y=.0) -> Vector2D:
|
|
179
|
+
return Vector2D(self.x / (x + both), self.y / (y + both))
|
|
180
|
+
|
|
181
|
+
def fdiv(self, both=.0, x=.0, y=.0) -> Vector2D:
|
|
182
|
+
return Vector2D(self.x // (x + both), self.y // (y + both))
|
|
183
|
+
|
|
184
|
+
# fast inplace operations Vector2D.ioperation(both,x,y)
|
|
185
|
+
def set(self, both=.0, x=.0, y=.0) -> Vector2D:
|
|
186
|
+
self.x = x + both
|
|
187
|
+
self.y = y + both
|
|
188
|
+
return self
|
|
189
|
+
|
|
190
|
+
def iadd(self, both=.0, x=.0, y=.0) -> Vector2D:
|
|
191
|
+
self.x += x + both
|
|
192
|
+
self.y += y + both
|
|
193
|
+
return self
|
|
194
|
+
|
|
195
|
+
def isub(self, both=.0, x=.0, y=.0) -> Vector2D:
|
|
196
|
+
self.x -= x + both
|
|
197
|
+
self.y -= y + both
|
|
198
|
+
return self
|
|
199
|
+
|
|
200
|
+
def imult(self, both=.0, x=.0, y=.0) -> Vector2D:
|
|
201
|
+
self.x *= x + both
|
|
202
|
+
self.y *= y + both
|
|
203
|
+
return self
|
|
204
|
+
|
|
205
|
+
def ipow(self, both=.0, x=.0, y=.0) -> Vector2D:
|
|
206
|
+
self.x **= x + both
|
|
207
|
+
self.y **= y + both
|
|
208
|
+
return self
|
|
209
|
+
|
|
210
|
+
def imod(self, both=.0, x=.0, y=.0) -> Vector2D:
|
|
211
|
+
self.x %= x + both
|
|
212
|
+
self.y %= y + both
|
|
213
|
+
return self
|
|
214
|
+
|
|
215
|
+
def idiv(self, both=.0, x=.0, y=.0) -> Vector2D:
|
|
216
|
+
self.x /= x + both
|
|
217
|
+
self.y /= y + both
|
|
218
|
+
return self
|
|
219
|
+
|
|
220
|
+
def ifdiv(self, both=.0, x=.0, y=.0) -> Vector2D:
|
|
221
|
+
self.x //= x + both
|
|
222
|
+
self.y //= y + both
|
|
223
|
+
return self
|
|
694
224
|
|
|
695
225
|
# normal operations Vector2D + a
|
|
696
|
-
def __add__(self
|
|
697
|
-
other =
|
|
226
|
+
def __add__(self, other) -> "Vector2D":
|
|
227
|
+
other = Vector2D.__normalize__(other)
|
|
698
228
|
return Vector2D(self.x + other.x, self.y + other.y)
|
|
699
229
|
|
|
700
|
-
def __sub__(self
|
|
701
|
-
other =
|
|
230
|
+
def __sub__(self, other) -> "Vector2D":
|
|
231
|
+
other = Vector2D.__normalize__(other)
|
|
702
232
|
return Vector2D(self.x - other.x, self.y - other.y)
|
|
703
233
|
|
|
704
|
-
def __mul__(self
|
|
705
|
-
other =
|
|
234
|
+
def __mul__(self, other) -> "Vector2D":
|
|
235
|
+
other = Vector2D.__normalize__(other)
|
|
706
236
|
return Vector2D(self.x * other.x, self.y * other.y)
|
|
707
237
|
|
|
708
|
-
def __mod__(self
|
|
709
|
-
other =
|
|
238
|
+
def __mod__(self, other) -> "Vector2D":
|
|
239
|
+
other = Vector2D.__normalize__(other)
|
|
710
240
|
return Vector2D(self.x % other.x, self.y % other.y)
|
|
711
241
|
|
|
712
|
-
def __pow__(self
|
|
713
|
-
other =
|
|
242
|
+
def __pow__(self, other) -> "Vector2D":
|
|
243
|
+
other = Vector2D.__normalize__(other)
|
|
714
244
|
return Vector2D(self.x ** other.x, self.y ** other.y)
|
|
715
245
|
|
|
716
|
-
def __truediv__(self
|
|
717
|
-
other =
|
|
246
|
+
def __truediv__(self, other) -> "Vector2D":
|
|
247
|
+
other = Vector2D.__normalize__(other)
|
|
718
248
|
return Vector2D(self.x / other.x, self.y / other.y)
|
|
719
249
|
|
|
720
|
-
def __floordiv__(self
|
|
721
|
-
other =
|
|
250
|
+
def __floordiv__(self, other) -> "Vector2D":
|
|
251
|
+
other = Vector2D.__normalize__(other)
|
|
722
252
|
return Vector2D(self.x // other.x, self.y // other.y)
|
|
723
253
|
|
|
724
254
|
# right operations a + Vector2D
|
|
725
|
-
def __radd__(self
|
|
255
|
+
def __radd__(self, other) -> "Vector2D":
|
|
726
256
|
return self.__add__(other)
|
|
727
257
|
|
|
728
|
-
def __rsub__(self
|
|
729
|
-
other =
|
|
258
|
+
def __rsub__(self, other) -> "Vector2D":
|
|
259
|
+
other = Vector2D.__normalize__(other)
|
|
730
260
|
return Vector2D(other.x - self.x, other.y - self.y)
|
|
731
261
|
|
|
732
|
-
def __rmul__(self
|
|
262
|
+
def __rmul__(self, other) -> "Vector2D":
|
|
733
263
|
return self.__mul__(other)
|
|
734
264
|
|
|
735
|
-
def __rmod__(self
|
|
736
|
-
other =
|
|
265
|
+
def __rmod__(self, other) -> "Vector2D":
|
|
266
|
+
other = Vector2D.__normalize__(other)
|
|
737
267
|
return Vector2D(other.x % self.x, other.y % self.y)
|
|
738
268
|
|
|
739
|
-
def __rpow__(self
|
|
740
|
-
other =
|
|
269
|
+
def __rpow__(self, other) -> "Vector2D":
|
|
270
|
+
other = Vector2D.__normalize__(other)
|
|
741
271
|
return Vector2D(other.x ** self.x, other.y ** self.y)
|
|
742
272
|
|
|
743
|
-
def __rtruediv__(self
|
|
744
|
-
other =
|
|
273
|
+
def __rtruediv__(self, other) -> "Vector2D":
|
|
274
|
+
other = Vector2D.__normalize__(other)
|
|
745
275
|
return Vector2D(other.x / self.x, other.y / self.y)
|
|
746
276
|
|
|
747
|
-
def __rfloordiv__(self
|
|
748
|
-
other =
|
|
277
|
+
def __rfloordiv__(self, other) -> "Vector2D":
|
|
278
|
+
other = Vector2D.__normalize__(other)
|
|
749
279
|
return Vector2D(other.x // self.x, other.y // self.y)
|
|
750
280
|
|
|
751
281
|
# in-place operations Vector2D += a
|
|
752
|
-
def __iadd__(self
|
|
753
|
-
other =
|
|
282
|
+
def __iadd__(self, other) -> "Vector2D":
|
|
283
|
+
other = Vector2D.__normalize__(other)
|
|
754
284
|
self.x += other.x
|
|
755
285
|
self.y += other.y
|
|
756
286
|
return self
|
|
757
287
|
|
|
758
|
-
def __isub__(self
|
|
759
|
-
other =
|
|
288
|
+
def __isub__(self, other) -> "Vector2D":
|
|
289
|
+
other = Vector2D.__normalize__(other)
|
|
760
290
|
self.x -= other.x
|
|
761
291
|
self.y -= other.y
|
|
762
292
|
return self
|
|
763
293
|
|
|
764
|
-
def __imul__(self
|
|
765
|
-
other =
|
|
294
|
+
def __imul__(self, other) -> "Vector2D":
|
|
295
|
+
other = Vector2D.__normalize__(other)
|
|
766
296
|
self.x *= other.x
|
|
767
297
|
self.y *= other.y
|
|
768
298
|
return self
|
|
769
299
|
|
|
770
|
-
def __itruediv__(self
|
|
771
|
-
other =
|
|
300
|
+
def __itruediv__(self, other) -> "Vector2D":
|
|
301
|
+
other = Vector2D.__normalize__(other)
|
|
772
302
|
self.x /= other.x
|
|
773
303
|
self.y /= other.y
|
|
774
304
|
return self
|
|
775
305
|
|
|
776
|
-
def __imod__(self
|
|
777
|
-
other =
|
|
306
|
+
def __imod__(self, other) -> "Vector2D":
|
|
307
|
+
other = Vector2D.__normalize__(other)
|
|
778
308
|
self.x %= other.x
|
|
779
309
|
self.y %= other.y
|
|
780
310
|
return self
|
|
781
311
|
|
|
782
|
-
def __ipow__(self
|
|
783
|
-
other =
|
|
312
|
+
def __ipow__(self, other) -> "Vector2D":
|
|
313
|
+
other = Vector2D.__normalize__(other)
|
|
784
314
|
self.x **= other.x
|
|
785
315
|
self.y **= other.y
|
|
786
316
|
return self
|
|
787
317
|
|
|
788
|
-
def __ifloordiv__(self
|
|
789
|
-
other =
|
|
318
|
+
def __ifloordiv__(self, other) -> "Vector2D":
|
|
319
|
+
other = Vector2D.__normalize__(other)
|
|
790
320
|
self.x //= other.x
|
|
791
321
|
self.y //= other.y
|
|
792
322
|
return self
|
|
793
323
|
|
|
794
324
|
# comparasion
|
|
795
325
|
def __eq__(self, other) -> bool:
|
|
796
|
-
try: other =
|
|
326
|
+
try: other = Vector2D.__normalize__(other)
|
|
797
327
|
except: return False
|
|
798
328
|
return self.x == other.x and self.y == other.y
|
|
799
329
|
|
|
800
330
|
def __ne__(self, other) -> bool:
|
|
801
331
|
return not self.__eq__(other)
|
|
802
332
|
|
|
803
|
-
def __abs__(self
|
|
333
|
+
def __abs__(self) -> "Vector2D":
|
|
804
334
|
return Vector2D(abs(self.x), abs(self.y))
|
|
805
335
|
|
|
806
|
-
def __round__(self
|
|
807
|
-
n =
|
|
336
|
+
def __round__(self, n=1) -> "Vector2D":
|
|
337
|
+
n = Vector2D.__normalize__(n)
|
|
808
338
|
return Vector2D(round(self.x / n.x) * n.x, round(self.y / n.y) * n.y)
|
|
809
339
|
|
|
810
|
-
def __floor__(self
|
|
811
|
-
n =
|
|
812
|
-
return Vector2D(
|
|
340
|
+
def __floor__(self, n=1) -> "Vector2D":
|
|
341
|
+
n = Vector2D.__normalize__(n)
|
|
342
|
+
return Vector2D((self.x / n.x).__floor__() * n.x, (self.y / n.y).__floor__() * n.y)
|
|
813
343
|
|
|
814
|
-
def __ceil__(self
|
|
815
|
-
n =
|
|
816
|
-
return Vector2D(
|
|
344
|
+
def __ceil__(self, n=1) -> "Vector2D":
|
|
345
|
+
n = Vector2D.__normalize__(n)
|
|
346
|
+
return Vector2D((self.x / n.x).__ceil__() * n.x, (self.y / n.y).__ceil__() * n.y)
|
|
817
347
|
|
|
818
|
-
def __float__(self
|
|
348
|
+
def __float__(self) -> "Vector2D":
|
|
819
349
|
return Vector2D(float(self.x), float(self.y))
|
|
820
350
|
|
|
821
|
-
def __getitem__(self
|
|
822
|
-
if n
|
|
351
|
+
def __getitem__(self, n) -> int|float:
|
|
352
|
+
if n == 0 or n == "x":
|
|
823
353
|
return self.x
|
|
824
|
-
elif n
|
|
354
|
+
elif n == 1 or n == "y":
|
|
825
355
|
return self.y
|
|
826
356
|
else:
|
|
827
357
|
raise IndexError("V2 has only x,y...")
|
|
828
358
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
from .cvb import *
|
|
843
|
-
|
|
844
|
-
V2 = Vector2D
|
|
845
|
-
|
|
846
|
-
V2inf = Vector2D(float('inf'), float('inf'))
|
|
847
|
-
V2z = VectorZero = Vector2D()
|
|
848
|
-
V2one = Vector2D(1.0, 1.0)
|
|
849
|
-
|
|
850
|
-
def rgb(r:float, g:float, b:float) -> tuple[float, float, float]:
|
|
851
|
-
return (r,g,b)
|
|
852
|
-
|
|
853
|
-
def color_lerp(current_c:list|tuple, final_c:list|tuple, step:int|float=.1) -> tuple[float, float, float]:
|
|
854
|
-
"""
|
|
855
|
-
# Linearly interpolate between two colors.
|
|
856
|
-
|
|
857
|
-
## Parameters:
|
|
858
|
-
current_c (tuple or list): The RGB values of the current color as a tuple or list.
|
|
859
|
-
final_c (tuple or list): The RGB values of the target color as a tuple or list.
|
|
860
|
-
step (int or float): The interpolation step, ranging from 0.0 (current color) to 1.0 (target color).
|
|
861
|
-
|
|
862
|
-
## Returns:
|
|
863
|
-
tuple: The RGB values of the interpolated color as a tuple.
|
|
864
|
-
|
|
865
|
-
## Example:
|
|
866
|
-
current_c = (255, 0, 0)
|
|
867
|
-
|
|
868
|
-
final_c = (0, 0, 255)
|
|
869
|
-
|
|
870
|
-
step = 0.5
|
|
871
|
-
|
|
872
|
-
interpolated_color = color_lerp(current_c, final_c, step)
|
|
873
|
-
|
|
874
|
-
print(f"At step {step}: RGB {interpolated_color}")
|
|
875
|
-
|
|
876
|
-
This will calculate the color at an interpolation step of 0.5 between (255, 0, 0) and (0, 0, 255).
|
|
877
|
-
"""
|
|
878
|
-
return tuple(c + (final_c[i] - c) * step for i,c in enumerate(current_c)) #type: ignore
|
|
879
|
-
|
|
880
|
-
def color_fade(starting_c:list|tuple, final_c:list|tuple, index:int|float, max_index:int|float) -> tuple[float, float, float]:
|
|
881
|
-
"""
|
|
882
|
-
# Calculate the color at a specific index of a color fade between two given colors.
|
|
883
|
-
|
|
884
|
-
## Parameters:
|
|
885
|
-
starting_c (tuple or list): The RGB values of the starting color as a tuple or list.
|
|
886
|
-
final_c (tuple or list): The RGB values of the final color as a tuple or list.
|
|
887
|
-
index (int or float): The current index of the color fade, representing a position
|
|
888
|
-
between the starting and final colors.
|
|
889
|
-
max_index (int or float): The maximum index of the color fade, indicating the endpoint
|
|
890
|
-
position between the starting and final colors.
|
|
891
|
-
|
|
892
|
-
## Returns:
|
|
893
|
-
tuple: The RGB values of the color at the specified index as a tuple.
|
|
894
|
-
|
|
895
|
-
## Example:
|
|
896
|
-
starting_c = (255, 0, 0)
|
|
897
|
-
|
|
898
|
-
final_c = (0, 0, 255)
|
|
899
|
-
|
|
900
|
-
max_index = 100
|
|
901
|
-
|
|
902
|
-
for i in range(max_index + 1):
|
|
903
|
-
|
|
904
|
-
color_at_index = color_fade(starting_c, final_c, i, max_index)
|
|
905
|
-
|
|
906
|
-
print(f"At index {i}: RGB {color_at_index}")
|
|
359
|
+
@classmethod
|
|
360
|
+
def __normalize__(cls, other) -> "Vector2D":
|
|
361
|
+
if isinstance(other, Vector2D):
|
|
362
|
+
return other
|
|
363
|
+
if isinstance(other, (int, float)):
|
|
364
|
+
return cls(other, other)
|
|
365
|
+
if isinstance(other, (list, tuple)):
|
|
366
|
+
return cls(*other[:2])
|
|
367
|
+
try:
|
|
368
|
+
return cls(other.x, other.y)
|
|
369
|
+
except:
|
|
370
|
+
raise TypeError(f"The value {other} of type {type(other)} is not a num type: [{int|float}] nor an array type: [{list|tuple}]")
|
|
907
371
|
|
|
908
|
-
|
|
909
|
-
""
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
""
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
""
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
""
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
final_c (list or tuple): The RGB values of the final color.
|
|
951
|
-
sqrd (bool, optional): If True, return the squared distance. If False, return
|
|
952
|
-
the actual distance. Default is True.
|
|
953
|
-
|
|
954
|
-
## Returns:
|
|
955
|
-
float: The squared distance between the two colors if `sqrd` is True, otherwise
|
|
956
|
-
the actual distance.
|
|
957
|
-
|
|
958
|
-
## Example:
|
|
959
|
-
starting_c = [255, 0, 0]
|
|
372
|
+
@classmethod
|
|
373
|
+
def zero(cls) -> "Vector2D": return V2zero
|
|
374
|
+
@classmethod
|
|
375
|
+
def one(cls) -> "Vector2D": return V2one
|
|
376
|
+
@classmethod
|
|
377
|
+
def two(cls) -> "Vector2D": return V2two
|
|
378
|
+
@classmethod
|
|
379
|
+
def pi(cls) -> "Vector2D": return V2pi
|
|
380
|
+
@classmethod
|
|
381
|
+
def inf(cls) -> "Vector2D": return V2inf
|
|
382
|
+
@classmethod
|
|
383
|
+
def neg_one(cls) -> "Vector2D": return V2neg_one
|
|
384
|
+
@classmethod
|
|
385
|
+
def neg_two(cls) -> "Vector2D": return V2neg_two
|
|
386
|
+
@classmethod
|
|
387
|
+
def neg_pi(cls) -> "Vector2D": return V2neg_pi
|
|
388
|
+
@classmethod
|
|
389
|
+
def neg_inf(cls) -> "Vector2D": return V2neg_inf
|
|
390
|
+
@classmethod
|
|
391
|
+
def up(cls) -> "Vector2D": return V2up
|
|
392
|
+
@classmethod
|
|
393
|
+
def right(cls) -> "Vector2D": return V2right
|
|
394
|
+
@classmethod
|
|
395
|
+
def down(cls) -> "Vector2D": return V2down
|
|
396
|
+
@classmethod
|
|
397
|
+
def left(cls) -> "Vector2D": return V2left
|
|
398
|
+
@classmethod
|
|
399
|
+
def up_right(cls) -> "Vector2D": return V2up_right
|
|
400
|
+
@classmethod
|
|
401
|
+
def down_right(cls) -> "Vector2D": return V2down_right
|
|
402
|
+
@classmethod
|
|
403
|
+
def up_left(cls) -> "Vector2D": return V2up_left
|
|
404
|
+
@classmethod
|
|
405
|
+
def down_left(cls) -> "Vector2D": return V2down_left
|
|
406
|
+
@classmethod
|
|
407
|
+
def up_right_norm(cls) -> "Vector2D": return V2up_right_norm
|
|
408
|
+
@classmethod
|
|
409
|
+
def down_right_norm(cls) -> "Vector2D": return V2down_right_norm
|
|
410
|
+
@classmethod
|
|
411
|
+
def up_left_norm(cls) -> "Vector2D": return V2up_left_norm
|
|
412
|
+
@classmethod
|
|
413
|
+
def down_left_norm(cls) -> "Vector2D": return V2down_left_norm
|
|
960
414
|
|
|
961
|
-
|
|
415
|
+
@classmethod
|
|
416
|
+
def new_zero(cls) -> "Vector2D": return V2zero.copy
|
|
417
|
+
@classmethod
|
|
418
|
+
def new_one(cls) -> "Vector2D": return V2one.copy
|
|
419
|
+
@classmethod
|
|
420
|
+
def new_two(cls) -> "Vector2D": return V2two.copy
|
|
421
|
+
@classmethod
|
|
422
|
+
def new_pi(cls) -> "Vector2D": return V2pi.copy
|
|
423
|
+
@classmethod
|
|
424
|
+
def new_inf(cls) -> "Vector2D": return V2inf.copy
|
|
425
|
+
@classmethod
|
|
426
|
+
def new_neg_one(cls) -> "Vector2D": return V2neg_one.copy
|
|
427
|
+
@classmethod
|
|
428
|
+
def new_neg_two(cls) -> "Vector2D": return V2neg_two.copy
|
|
429
|
+
@classmethod
|
|
430
|
+
def new_neg_pi(cls) -> "Vector2D": return V2neg_pi.copy
|
|
431
|
+
@classmethod
|
|
432
|
+
def new_neg_inf(cls) -> "Vector2D": return V2neg_inf.copy
|
|
433
|
+
@classmethod
|
|
434
|
+
def new_up(cls) -> "Vector2D": return V2up.copy
|
|
435
|
+
@classmethod
|
|
436
|
+
def new_right(cls) -> "Vector2D": return V2right.copy
|
|
437
|
+
@classmethod
|
|
438
|
+
def new_down(cls) -> "Vector2D": return V2down.copy
|
|
439
|
+
@classmethod
|
|
440
|
+
def new_left(cls) -> "Vector2D": return V2left.copy
|
|
441
|
+
@classmethod
|
|
442
|
+
def new_up_right(cls) -> "Vector2D": return V2up_right.copy
|
|
443
|
+
@classmethod
|
|
444
|
+
def new_down_right(cls) -> "Vector2D": return V2down_right.copy
|
|
445
|
+
@classmethod
|
|
446
|
+
def new_up_left(cls) -> "Vector2D": return V2up_left.copy
|
|
447
|
+
@classmethod
|
|
448
|
+
def new_down_left(cls) -> "Vector2D": return V2down_left.copy
|
|
449
|
+
@classmethod
|
|
450
|
+
def new_up_right_norm(cls) -> "Vector2D": return V2up_right_norm.copy
|
|
451
|
+
@classmethod
|
|
452
|
+
def new_down_right_norm(cls) -> "Vector2D": return V2down_right_norm.copy
|
|
453
|
+
@classmethod
|
|
454
|
+
def new_up_left_norm(cls) -> "Vector2D": return V2up_left_norm.copy
|
|
455
|
+
@classmethod
|
|
456
|
+
def new_down_left_norm(cls) -> "Vector2D": return V2down_left_norm.copy
|
|
962
457
|
|
|
963
|
-
|
|
458
|
+
from .cvb import *
|
|
964
459
|
|
|
965
|
-
|
|
460
|
+
V2 = Vector2D
|
|
966
461
|
|
|
967
|
-
|
|
462
|
+
V2zero = Vector2D(0, 0)
|
|
968
463
|
|
|
969
|
-
|
|
464
|
+
V2one = Vector2D(1.0, 1.0)
|
|
465
|
+
V2two = Vector2D(2.0, 2.0)
|
|
466
|
+
V2pi = Vector2D(PI, PI)
|
|
467
|
+
V2inf = Vector2D(float("inf"), float("inf"))
|
|
970
468
|
|
|
971
|
-
|
|
469
|
+
V2neg_one = Vector2D(1.0, 1.0)
|
|
470
|
+
V2neg_two = Vector2D(2.0, 2.0)
|
|
471
|
+
V2neg_pi = Vector2D(PI, PI)
|
|
472
|
+
V2neg_inf = Vector2D(float("inf"), float("inf"))
|
|
972
473
|
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
root of this sum.
|
|
474
|
+
V2up = Vector2D(0, 1)
|
|
475
|
+
V2right = Vector2D(1, 0)
|
|
476
|
+
V2down = Vector2D(0, -1)
|
|
477
|
+
V2left = Vector2D(-1, 0)
|
|
978
478
|
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
distance = sum([(starting_c[i]-final_c[i])**2 for i in range(3)])
|
|
984
|
-
return (distance ** .5) if sqrd else distance
|
|
479
|
+
V2up_right = Vector2D(1, 1)
|
|
480
|
+
V2down_right = Vector2D(1, -1)
|
|
481
|
+
V2up_left = Vector2D(-1, 1)
|
|
482
|
+
V2down_left = Vector2D(-1, -1)
|
|
985
483
|
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
484
|
+
V2up_right_norm = V2up_right.normalize
|
|
485
|
+
V2down_right_norm = V2down_right.normalize
|
|
486
|
+
V2up_left_norm = V2up_left.normalize
|
|
487
|
+
V2down_left_norm = V2down_left.normalize
|
|
989
488
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
489
|
+
VECTORS_4_DIRECTIONS = (V2right, V2down, V2left, V2up)
|
|
490
|
+
VECTORS_4_SEMIDIRECTIONS = (V2down_right, V2down_left, V2up_left, V2up_right)
|
|
491
|
+
VECTORS_4_SEMIDIRECTIONS_NORM = (V2down_right_norm, V2down_left_norm, V2up_left_norm, V2up_right_norm)
|
|
492
|
+
VECTORS_8_DIRECTIONS = (V2right, V2down_right, V2down, V2down_left, V2left, V2up_left, V2up, V2up_right)
|
|
493
|
+
VECTORS_8_DIRECTIONS_NORM = (V2right, V2down_right_norm, V2down, V2down_left_norm, V2left, V2up_left_norm, V2up, V2up_right_norm)
|
|
994
494
|
|
|
995
|
-
## Returns:
|
|
996
|
-
float: The interpolated angle as a result of angular interpolation.
|
|
997
495
|
|
|
998
|
-
|
|
999
|
-
|
|
496
|
+
def rgb(r:float, g:float, b:float) -> tuple[float, float, float]:
|
|
497
|
+
return (r,g,b)
|
|
1000
498
|
|
|
1001
|
-
|
|
499
|
+
# def color_lerp(current_c:list|tuple, final_c:list|tuple, step=.1) -> tuple[float, float, float]:
|
|
500
|
+
# return tuple(c + (final_c[i] - c) * step for i,c in enumerate(current_c)) #type: ignore
|
|
1002
501
|
|
|
1003
|
-
|
|
502
|
+
# def color_fade(starting_c:list|tuple, final_c:list|tuple, index, max_index) -> tuple[float, float, float]:
|
|
503
|
+
# return tuple((starting_c[i] - final_c[i]) / max_index * (max_index - index) + final_c[i] for i in range(3)) #type: ignore
|
|
1004
504
|
|
|
1005
|
-
|
|
505
|
+
# def weighted_color_fade(colors_dict:dict) -> tuple[float, float, float]:
|
|
506
|
+
# colors = colors_dict.keys()
|
|
507
|
+
# weights = colors_dict.values()
|
|
1006
508
|
|
|
1007
|
-
|
|
509
|
+
# if float("inf") in weights: return list(colors)[list(weights).index(float("inf"))]
|
|
510
|
+
# return tuple(sum(n[i]*w for n,w in zip(colors, weights)) / sum(weights) for i in range(3)) #type: ignore
|
|
1008
511
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
1. The direct interpolation from `starting_angle` to `final_angle`.
|
|
1013
|
-
2. The interpolation by taking a full circle (2 * pi) and then proceeding from
|
|
1014
|
-
`starting_angle` to `final_angle`.
|
|
1015
|
-
3. The interpolation by taking a full circle (2 * pi) in the opposite direction
|
|
1016
|
-
and then proceeding from `starting_angle` to `final_angle`.
|
|
512
|
+
# def color_distance(starting_c:list|tuple, final_c:list|tuple, sqrd) -> float:
|
|
513
|
+
# distance = sum([(starting_c[i]-final_c[i])**2 for i in range(3)])
|
|
514
|
+
# return (distance ** .5) if sqrd else distance
|
|
1017
515
|
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
value.
|
|
516
|
+
def lerp(starting, ending, step=.1) -> float:
|
|
517
|
+
return starting + (ending - starting) * step
|
|
1021
518
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
519
|
+
def angular_interpolation(starting_angle, final_angle, step=.1) -> float:
|
|
520
|
+
# my way
|
|
521
|
+
# delta = final_angle - starting_angle
|
|
522
|
+
# return starting_angle + min((delta, delta - DOUBLE_PI, delta + DOUBLE_PI), key=abs) * step
|
|
523
|
+
|
|
524
|
+
# math way
|
|
525
|
+
shortest_angle = ((((final_angle - starting_angle) % DOUBLE_PI) + DOUBLE_PI * 1.5) % DOUBLE_PI) - PI
|
|
526
|
+
return starting_angle + shortest_angle * step
|
|
1027
527
|
|
|
1028
|
-
def bezier_cubic_interpolation(t
|
|
528
|
+
def bezier_cubic_interpolation(t, p0, p1) -> float:
|
|
1029
529
|
return t*p0.y*3*(1 - t)**2 + p1.y*3*(1 - t) * t**2 + t**3
|
|
1030
530
|
|
|
1031
|
-
def bezier_quadratic_interpolation(t
|
|
531
|
+
def bezier_quadratic_interpolation(t, p0) -> float:
|
|
1032
532
|
return 2*(1-t)*t*p0.y+t**2
|
|
1033
533
|
|
|
1034
|
-
def avg_position(*others
|
|
1035
|
-
"""
|
|
1036
|
-
# Calculate the average position for a variable number of Vector2D others.
|
|
1037
|
-
|
|
1038
|
-
## Parameters:
|
|
1039
|
-
*others (Vector2D): Variable number of Vector2D others representing positions.
|
|
1040
|
-
|
|
1041
|
-
## Returns:
|
|
1042
|
-
Vector2D: The average position as a new Vector2D other.
|
|
1043
|
-
|
|
1044
|
-
## Example:
|
|
1045
|
-
position1 = Vector2D(10, 20)
|
|
1046
|
-
|
|
1047
|
-
position2 = Vector2D(30, 40)
|
|
1048
|
-
|
|
1049
|
-
position3 = Vector2D(50, 60)
|
|
1050
|
-
|
|
1051
|
-
average_pos = avg_position(position1, position2, position3)
|
|
1052
|
-
|
|
1053
|
-
print(average_pos)
|
|
1054
|
-
|
|
1055
|
-
This will print the average position of the three Vector2D others.
|
|
1056
|
-
|
|
1057
|
-
## Explanation:
|
|
1058
|
-
The function takes a variable number of Vector2D others as input, representing positions.
|
|
1059
|
-
It calculates the sum of all the Vector2D others using the `sum` function and then divides
|
|
1060
|
-
it by the total number of others (length of `others`) to find the average position.
|
|
1061
|
-
|
|
1062
|
-
The result is returned as a new Vector2D other representing the average position.
|
|
1063
|
-
"""
|
|
534
|
+
def avg_position(*others) -> Vector2D:
|
|
1064
535
|
return sum(others) / len(others) #type: ignore
|
|
1065
536
|
|
|
1066
|
-
def inter_points(ray
|
|
1067
|
-
""
|
|
1068
|
-
# Find intersection points between a ray or line segment and multiple line segments.
|
|
1069
|
-
|
|
1070
|
-
## Parameters:
|
|
1071
|
-
ray (list[Vector2D] | tuple[Vector2D, Vector2D]): The ray or line segment represented by two endpoints
|
|
1072
|
-
(start and end points).
|
|
1073
|
-
lines (list[tuple[Vector2D, Vector2D]]): A list of line segments represented by tuples of their endpoints.
|
|
1074
|
-
return_inter_lines (bool, optional): If True, return a list of tuples containing the intersection points and the
|
|
1075
|
-
corresponding intersecting line segment. Default is False.
|
|
1076
|
-
sort (bool, optional): If True, sort the intersection points by their distance from the ray's start point.
|
|
1077
|
-
Default is False.
|
|
1078
|
-
return_empty (bool, optional): If True, include None for line segments with no intersection. Default is False.
|
|
1079
|
-
|
|
1080
|
-
## Returns:
|
|
1081
|
-
list[tuple[Vector2D | None, tuple[Vector2D | V2, Vector2D | V2]]] | list[Vector2D | None]:
|
|
1082
|
-
- If return_inter_lines is True, returns a list of tuples, each containing:
|
|
1083
|
-
- The intersection point (Vector2D) if it exists, or None otherwise.
|
|
1084
|
-
- The corresponding intersecting line segment (tuple[Vector2D | V2, Vector2D | V2]).
|
|
1085
|
-
- If return_inter_lines is False, returns a list of intersection points (Vector2D) if they exist, or None otherwise.
|
|
1086
|
-
|
|
1087
|
-
# Example:
|
|
1088
|
-
ray = [Vector2D(1, 2), Vector2D(5, 3)]
|
|
1089
|
-
lines = [(Vector2D(2, 2), Vector2D(4, 4)), (Vector2D(3, 3), Vector2D(6, 2))]
|
|
1090
|
-
result = inter_points(ray, lines, return_inter_lines=True, sort=True)
|
|
1091
|
-
print(result)
|
|
1092
|
-
|
|
1093
|
-
## Explanation:
|
|
1094
|
-
The function finds the intersection points (if any) between the given ray (or line segment) and the provided list
|
|
1095
|
-
of line segments. The intersection points are returned in a list.
|
|
1096
|
-
|
|
1097
|
-
If return_inter_lines is True, the function returns a list of tuples, where each tuple contains the intersection point
|
|
1098
|
-
(Vector2D) and the corresponding intersecting line segment (tuple[Vector2D | V2, Vector2D | V2]). If return_inter_lines
|
|
1099
|
-
is False, the function returns only a list of intersection points (Vector2D) without the corresponding line segments.
|
|
1100
|
-
|
|
1101
|
-
If sort is True, the intersection points are sorted by their distance from the ray's start point. If sort is False,
|
|
1102
|
-
the intersection points are returned in the order they were found.
|
|
1103
|
-
|
|
1104
|
-
If return_empty is True, the function includes None for line segments with no intersection. If return_empty is False,
|
|
1105
|
-
line segments with no intersection are omitted from the result.
|
|
1106
|
-
|
|
1107
|
-
Example usage is shown in the "Example" section above.
|
|
1108
|
-
"""
|
|
1109
|
-
def lineLineIntersect(P0:"Vector2D", P1:"Vector2D", Q0:"Vector2D", Q1:"Vector2D") -> "Vector2D | None":
|
|
537
|
+
def inter_points(ray, lines, return_inter_lines=False, sort=False, return_empty=False) -> list[tuple[Vector2D | None, tuple[Vector2D, Vector2D]]] | list[Vector2D | None] | list[Vector2D]:
|
|
538
|
+
def lineLineIntersect(P0, P1, Q0, Q1) -> "Vector2D | None":
|
|
1110
539
|
d = (P1.x-P0.x) * (Q1.y-Q0.y) + (P1.y-P0.y) * (Q0.x-Q1.x)
|
|
1111
540
|
if d == 0:
|
|
1112
541
|
return None
|
|
@@ -1123,138 +552,34 @@ def inter_points(ray:list["Vector2D"]|tuple["Vector2D", "Vector2D"], lines:list[
|
|
|
1123
552
|
collisions = [ip for line in lines if ((ip:=lineLineIntersect(line[1], line[0], ray[1], ray[0]))!=None or return_empty)]
|
|
1124
553
|
return sorted(collisions, key=lambda x: ray[0].distance_to(x, False) if x != None else _mt.inf) if sort else collisions
|
|
1125
554
|
|
|
1126
|
-
def get_points(position
|
|
1127
|
-
"""
|
|
1128
|
-
# Generate points for a rectangle based on the given parameters.
|
|
1129
|
-
|
|
1130
|
-
## Parameters:
|
|
1131
|
-
position (Vector2D): The center position of the rectangle.
|
|
1132
|
-
size (Vector2D): The size of the rectangle (width and height).
|
|
1133
|
-
rotation (int|float, optional): The rotation angle in degrees. Default is 0.
|
|
1134
|
-
pos_in_middle (bool, optional): If True, the points represent corners of the rectangle.
|
|
1135
|
-
If False, the points represent the rectangle's edges.
|
|
1136
|
-
Default is True.
|
|
1137
|
-
return_list (bool, optional): If True, return the points as lists instead of Vector2D others.
|
|
1138
|
-
Default is False.
|
|
1139
|
-
clockwise_return (bool, optional): If True, return the points in clockwise order (A, B, D, C).
|
|
1140
|
-
If False, return the points in counterclockwise order (A, B, C, D).
|
|
1141
|
-
Default is False.
|
|
1142
|
-
|
|
1143
|
-
## Returns:
|
|
1144
|
-
tuple: A tuple containing the four points of the rectangle.
|
|
1145
|
-
|
|
1146
|
-
## Example:
|
|
1147
|
-
position = Vector2D(100, 100)
|
|
1148
|
-
|
|
1149
|
-
size = Vector2D(50, 30)
|
|
1150
|
-
|
|
1151
|
-
rotation = 45
|
|
1152
|
-
|
|
1153
|
-
points = get_points(position, size, rotation)
|
|
1154
|
-
|
|
1155
|
-
print(points)
|
|
1156
|
-
|
|
1157
|
-
This will print the four points of the rotated rectangle.
|
|
1158
|
-
|
|
1159
|
-
## Explanation:
|
|
1160
|
-
The function calculates the four points (A, B, C, D) of the rectangle based on the center position,
|
|
1161
|
-
size, rotation, and pos_in_middle parameters. The points represent the rectangle's corners if pos_in_middle
|
|
1162
|
-
is True, and the edges if pos_in_middle is False.
|
|
1163
|
-
|
|
1164
|
-
The points are returned as Vector2D others unless the return_list parameter is set to True. In that case,
|
|
1165
|
-
the points will be returned as lists.
|
|
1166
|
-
|
|
1167
|
-
The clockwise_return parameter determines the order of the points. If True, the points will be returned in
|
|
1168
|
-
clockwise order (A, B, D, C), otherwise, they will be returned in counterclockwise order (A, B, C, D).
|
|
1169
|
-
"""
|
|
555
|
+
def get_points(position, size, rotation=0, pos_in_middle=True, return_list=False, clockwise_return=False) -> tuple["Vector2D", "Vector2D", "Vector2D", "Vector2D"] | tuple[list[int|float]|tuple[int|float], list[int|float]|tuple[int|float], list[int|float]|tuple[int|float], list[int|float]|tuple[int|float]]:
|
|
1170
556
|
if pos_in_middle:
|
|
1171
|
-
d,a = size.length
|
|
1172
|
-
d1, d2 =
|
|
557
|
+
d,a = size.length/2, size.angle
|
|
558
|
+
d1, d2 = Vector2D.zero().point_from_angle_and_radius(rotation+a, d), Vector2D.zero().point_from_angle_and_radius(rotation-a, d)
|
|
1173
559
|
A, B, C, D = position+d1, position+d2, position-d2, position-d1
|
|
1174
560
|
else:
|
|
1175
|
-
A, B, C, D = position.copy
|
|
1176
|
-
position.
|
|
1177
|
-
position.
|
|
1178
|
-
position.
|
|
561
|
+
A, B, C, D = position.copy,\
|
|
562
|
+
position.point_from_angle_and_radius(rotation + Vector2D.zero().angle_to(Vector2D(size.x, 0)), Vector2D.zero().distance_to(Vector2D(size.x, 0))),\
|
|
563
|
+
position.point_from_angle_and_radius(rotation + Vector2D.zero().angle_to(Vector2D(0, size.y)), Vector2D.zero().distance_to(Vector2D(0, size.y))),\
|
|
564
|
+
position.point_from_angle_and_radius(rotation + Vector2D.zero().angle_to(size), Vector2D.zero().distance_to(size))
|
|
1179
565
|
points = (A, B, C, D) if not clockwise_return else (A, B, D, C)
|
|
1180
566
|
return points if not return_list else tuple(x() for x in points)
|
|
1181
567
|
|
|
1182
|
-
def get_lines(position
|
|
1183
|
-
"""
|
|
1184
|
-
# Generate lines representing the sides of a rectangle based on the given parameters.
|
|
1185
|
-
|
|
1186
|
-
## Parameters:
|
|
1187
|
-
position (Vector2D): The center position of the rectangle.
|
|
1188
|
-
size (Vector2D): The size of the rectangle (width and height).
|
|
1189
|
-
rotation (int|float, optional): The rotation angle in degrees. Default is 0.
|
|
1190
|
-
pos_in_middle (bool, optional): If True, the points represent corners of the rectangle.
|
|
1191
|
-
If False, the points represent the rectangle's edges.
|
|
1192
|
-
Default is True.
|
|
1193
|
-
|
|
1194
|
-
## Returns:
|
|
1195
|
-
list[list[Vector2D]]: A list of lists, where each sublist contains two Vector2D others
|
|
1196
|
-
representing the start and end points of a line segment.
|
|
1197
|
-
|
|
1198
|
-
## Example:
|
|
1199
|
-
position = Vector2D(100, 100)
|
|
1200
|
-
|
|
1201
|
-
size = Vector2D(50, 30)
|
|
1202
|
-
|
|
1203
|
-
rotation = 45
|
|
1204
|
-
|
|
1205
|
-
lines = get_lines(position, size, rotation)
|
|
1206
|
-
|
|
1207
|
-
print(lines)
|
|
1208
|
-
|
|
1209
|
-
This will print the four line segments representing the sides of the rotated rectangle.
|
|
1210
|
-
|
|
1211
|
-
## Explanation:
|
|
1212
|
-
The function calculates the four points (A, B, C, D) of the rectangle using the `get_points` function
|
|
1213
|
-
based on the center position, size, rotation, and pos_in_middle parameters.
|
|
1214
|
-
|
|
1215
|
-
The function then returns a list of lists, where each sublist contains two Vector2D others representing
|
|
1216
|
-
the start and end points of a line segment forming the sides of the rectangle.
|
|
1217
|
-
"""
|
|
568
|
+
def get_lines(position, size, rotation=0, pos_in_middle=True) -> list[list]:
|
|
1218
569
|
A, B, C, D = get_points(position, size, rotation, pos_in_middle)
|
|
1219
570
|
return [[A, B], [A, C], [C, D], [D, B]]
|
|
1220
571
|
|
|
1221
|
-
def distance_line_point(line_point_a
|
|
1222
|
-
|
|
1223
|
-
#
|
|
1224
|
-
|
|
1225
|
-
## Parameters:
|
|
1226
|
-
line_point_a (Vector2D): The starting point of the line segment.
|
|
1227
|
-
line_point_b (Vector2D): The ending point of the line segment.
|
|
1228
|
-
point_c (Vector2D): The point to which the distance is calculated.
|
|
1229
|
-
|
|
1230
|
-
## Returns:
|
|
1231
|
-
float: The distance between the line segment and the point.
|
|
1232
|
-
|
|
1233
|
-
## Example:
|
|
1234
|
-
line_point_a = Vector2D(0, 0)
|
|
1235
|
-
|
|
1236
|
-
line_point_b = Vector2D(10, 0)
|
|
1237
|
-
|
|
1238
|
-
point_c = Vector2D(5, 5)
|
|
1239
|
-
|
|
1240
|
-
distance = distance_line_point(line_point_a, line_point_b, point_c)
|
|
1241
|
-
|
|
1242
|
-
print(distance)
|
|
1243
|
-
|
|
1244
|
-
This will print the distance between the line segment and the point.
|
|
1245
|
-
|
|
1246
|
-
## Explanation:
|
|
1247
|
-
The function calculates the distance between a line segment defined by two points (line_point_a and line_point_b)
|
|
1248
|
-
and a third point (point_c).
|
|
1249
|
-
|
|
1250
|
-
It does this by first computing the cross product of vectors (line_point_b - line_point_a) and (line_point_a - point_c).
|
|
1251
|
-
The magnitude of the resulting vector is divided by the magnitude of (line_point_b - line_point_a) to obtain the distance.
|
|
572
|
+
def distance_line_point(line_point_a, line_point_b, point_c) -> float:
|
|
573
|
+
# numpy way
|
|
574
|
+
# return float(_np.linalg.norm(_np.cross((line_point_b-line_point_a)(), (line_point_a-point_c)()))/_np.linalg.norm((line_point_b-line_point_a)())) #type: ignore
|
|
1252
575
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
576
|
+
# math way
|
|
577
|
+
return abs((line_point_b.y - line_point_a.y) * point_c.x -\
|
|
578
|
+
(line_point_b.x - line_point_a.x) * point_c.y +\
|
|
579
|
+
line_point_b.x * line_point_a.y - line_point_b.y * line_point_a.x) /\
|
|
580
|
+
((line_point_b.y-line_point_a.y)**2 + (line_point_b.x-line_point_a.x)**2)**.5
|
|
1256
581
|
|
|
1257
|
-
def optimize_value_string(value
|
|
582
|
+
def optimize_value_string(value, precision) -> str:
|
|
1258
583
|
abs_value = abs(value)
|
|
1259
584
|
if abs_value < 1/10**precision and abs_value != 0:
|
|
1260
585
|
return f"{value:.{precision}e}"
|