e2D 1.4.0__tar.gz → 1.4.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: e2D
3
- Version: 1.4.0
3
+ Version: 1.4.2
4
4
  Summary: Python library for 2D games. Streamlines dev with keyboard/mouse input, vector calculations, color manipulation, and collision detection. Simplify game creation and unleash creativity!
5
5
  Home-page: https://github.com/marick-py/e2D
6
6
  Author: Riccardo Mariani
@@ -17,260 +17,36 @@ DOUBLE_PI = PI*2
17
17
  class Vector2D:
18
18
  round_values_on_print :int|float= 2
19
19
  def __init__(self:"V2|Vector2D", x:int|float=0.0, y:int|float=0.0) -> None:
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
- """
40
20
  self.x = x
41
21
  self.y = y
42
22
 
43
23
  def set(self:"V2|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
24
  self.x = x
68
25
  self.y = y
69
26
 
70
27
  def distance_to(self:"V2|Vector2D", other:"float|int|Vector2D|V2|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
-
86
- point2 = Vector2D(3, 4)
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
28
  other = self.__normalize__(other)
105
29
  d = (self.x - other.x)**2 + (self.y - other.y)**2
106
30
  return (d**(1/2) if sqrd else d)
107
31
 
108
32
  def angle_to(self:"V2|Vector2D", other:"float|int|Vector2D|V2|list|tuple") -> int|float:
109
- """
110
- # Calculate the angle between the current Vector2D other and another other.
111
-
112
- ## Parameters:
113
- other (float or int or Vector2D or list|tuple): The other other to which the angle is calculated.
114
-
115
- ## Returns:
116
- int|float: The angle in radians between the current Vector2D other and the other other.
117
-
118
- ## Example:
119
- point1 = Vector2D(0, 0)
120
-
121
- point2 = Vector2D(1, 1)
122
-
123
- angle = point1.angle_to(point2)
124
-
125
- print(f"Angle in radians: {angle}")
126
-
127
- This will calculate the angle in radians between the two points.
128
-
129
- ## Explanation:
130
- The function calculates the angle in radians between the current Vector2D other (self) and another other
131
- (other) using the `atan2` function from the `math` module.
132
-
133
- The result is returned as the angle in radians.
134
- """
135
33
  other = self.__normalize__(other)
136
34
  return _mt.atan2(other.y - self.y, other.x - self.x)
137
35
 
138
36
  def point_from_degs(self:"V2|Vector2D", degs:int|float, radius:int|float) -> "Vector2D|V2":
139
- """
140
- # Calculate a new Vector2D point from the current point based on an angle in degs and a radius.
141
-
142
- ## Parameters:
143
- rad (int|float): The angle in degs.
144
- radius (int|float): The distance from the current point.
145
-
146
- ## Returns:
147
- Vector2D: A new Vector2D point calculated from the current point.
148
-
149
- ## Example:
150
- point1 = Vector2D(0, 0)
151
-
152
- angle = 45
153
-
154
- distance = 5
155
-
156
- new_point = point1.point_from_degs(angle, distance)
157
-
158
- print(new_point.x, new_point.y)
159
-
160
- This will calculate a new point 5 units away from point1 at a 45-degree angle.
161
-
162
- ## Explanation:
163
- The function calculates a new Vector2D point based on an angle in degs (degs) and a distance (radius)
164
- from the current Vector2D point.
165
-
166
- It computes the new x and y coordinates of the point using the trigonometric functions `cos` and `sin`
167
- to determine the horizontal and vertical components of the new point.
168
-
169
- The result is returned as a new Vector2D point with the calculated coordinates.
170
- """
171
37
  x = radius * _mt.cos(_mt.radians(degs)) + self.x
172
38
  y = radius * _mt.sin(_mt.radians(degs)) + self.y
173
39
  return Vector2D(x, y)
174
40
 
175
41
  def point_from_rads(self:"V2|Vector2D", rad:int|float, radius:int|float) -> "Vector2D|V2":
176
- """
177
- # Calculate a new Vector2D point from the current point based on an angle in radians and a radius.
178
-
179
- ## Parameters:
180
- rad (int|float): The angle in radians.
181
- radius (int|float): The distance from the current point.
182
-
183
- ## Returns:
184
- Vector2D: A new Vector2D point calculated from the current point.
185
-
186
- ## Example:
187
- point1 = Vector2D(0, 0)
188
-
189
- angle = 45
190
-
191
- distance = 5
192
-
193
- new_point = point1.point_from_degs(_mt.radians(angle), distance)
194
-
195
- print(new_point.x, new_point.y)
196
-
197
- This will calculate a new point 5 units away from point1 at a 45-degree angle.
198
-
199
- ## Explanation:
200
- The function calculates a new Vector2D point based on an angle in radians (rad) and a distance (radius)
201
- from the current Vector2D point.
202
-
203
- It computes the new x and y coordinates of the point using the trigonometric functions `cos` and `sin`
204
- to determine the horizontal and vertical components of the new point.
205
-
206
- The result is returned as a new Vector2D point with the calculated coordinates.
207
- """
208
42
  x = radius * _mt.cos(rad) + self.x
209
43
  y = radius * _mt.sin(rad) + self.y
210
44
  return Vector2D(x, y)
211
45
 
212
46
  def copy(self:"V2|Vector2D") -> "Vector2D|V2":
213
- """
214
- # Create a copy of the current Vector2D other.
215
-
216
- ## Returns:
217
- Vector2D: A new Vector2D other with the same x and y coordinates as the current other.
218
-
219
- ## Example:
220
- point1 = Vector2D(1, 2)
221
-
222
- point2 = point1.copy()
223
-
224
- print(point2.x, point2.y)
225
-
226
- This will print the x and y coordinates of the copied Vector2D other (1, 2).
227
-
228
- ## Explanation:
229
- The function creates a new Vector2D other with the same x and y coordinates as the current other.
230
-
231
- The result is returned as a new Vector2D other, effectively making a copy of the original other.
232
- """
233
47
  return Vector2D(self.x, self.y)
234
48
 
235
49
  def sign(self:"V2|Vector2D") -> "Vector2D|V2":
236
- """
237
- # Perform an "absolute round" operation on the Vector2D other.
238
-
239
- ## Parameters:
240
- n (int|float, optional): The numeric value to scale the "absolute rounded" vector. Default is 1.
241
-
242
- ## Returns:
243
- Vector2D: The "absolute rounded" Vector2D other scaled by the provided numeric value.
244
-
245
- ## Example:
246
- vector1 = Vector2D(3.3, -4.7)
247
-
248
- result1 = vector1.absolute_round(0.5)
249
-
250
- print(result1.x, result1.y)
251
-
252
- vector2 = Vector2D(-2.8, 1.1)
253
-
254
- result2 = vector2.absolute_round()
255
-
256
- print(result2.x, result2.y)
257
-
258
- ## Explanation:
259
- The function performs an "absolute round" operation on the Vector2D other.
260
-
261
- The "absolute round" operation involves taking the absolute values of both the x and y components of the Vector2D other,
262
- and then scaling the resulting vector by the provided numeric value (n).
263
-
264
- The default value of n is 1, which means the "absolute rounded" vector will have the same magnitude as the original vector.
265
-
266
- If the provided numeric value (n) is 0, the function returns a Vector2D other with zeros for both components.
267
-
268
- If the provided numeric value (n) is negative, the resulting "absolute rounded" vector will point in the opposite direction
269
- as the original vector but will have the same magnitude.
270
-
271
- Note: The "absolute round" operation does not perform standard mathematical rounding; instead, it ensures the resulting
272
- vector points in the same direction as the original vector but has non-negative components.
273
- """
274
50
  return self.no_zero_div_error(abs(self), "zero")
275
51
 
276
52
  def floor(self:"V2|Vector2D", n:"int|float|Vector2D|V2"=1) -> "Vector2D|V2":
@@ -283,40 +59,6 @@ class Vector2D:
283
59
  return self.__round__(n)
284
60
 
285
61
  def randomize(start:"int|float|Vector2D|V2|None"=None, end:"int|float|Vector2D|V2|None"=None) -> "Vector2D|V2": #type: ignore
286
- """
287
- # Generate a random Vector2D point within the specified range.
288
-
289
- ## Parameters:
290
- start (int|float or Vector2D or None, optional): The starting point of the range.
291
- Default is None, which corresponds to (0, 0).
292
- If numeric, both x and y will have the same value.
293
- end (int|float or Vector2D or None, optional): The ending point of the range.
294
- Default is None, which corresponds to (1, 1).
295
- If numeric, both x and y will have the same value.
296
-
297
- ## Returns:
298
- Vector2D: A new random Vector2D point within the specified range.
299
-
300
- ## Example:
301
- random_point = randomize(Vector2D(10, 20), Vector2D(50, 70))
302
-
303
- print(random_point.x, random_point.y)
304
-
305
- This will print a random point between (10, 20) and (50, 70).
306
-
307
- ## Explanation:
308
- The function generates a random Vector2D point within the specified range defined by `start` and `end`.
309
-
310
- If `start` and `end` are numeric values (int or float), both x and y coordinates will have the same value.
311
-
312
- If `start` and `end` are None, the default range is assumed to be (0, 0) to (1, 1).
313
-
314
- The function first checks if `start` and `end` are Vector2D others. If not, it creates new Vector2D others
315
- based on the numeric values provided or the default values.
316
-
317
- It then generates random x and y coordinates in the range [0, 1) using the `random()` function from the `random` module.
318
- These random values are then scaled by (end - start) and added to the start point to obtain the final random Vector2D point.
319
- """
320
62
  if not any(isinstance(start, cls) for cls in {Vector2D, V2}):
321
63
  if type(start) in int|float: start = Vector2D(start, start) #type: ignore
322
64
  elif type(start) == None: start = Vector2D(0,0)
@@ -329,100 +71,15 @@ class Vector2D:
329
71
 
330
72
  def dot_product(self, other:"float|int|Vector2D|V2|list|tuple") -> float:
331
73
  other = self.__normalize__(other)
332
- """
333
- # Calculate the dot product of the current vector with another vector.
334
-
335
- ## Parameters:
336
- other (Vector2D): The other vector for the dot product calculation.
337
-
338
- ## Returns:
339
- float: The dot product value.
340
-
341
- ## Example:
342
- v1 = Vector2D(2, 3)
343
- v2 = Vector2D(4, -1)
344
- result = v1.dot_product(v2)
345
- print(result) # Output: 5
346
-
347
- ## Explanation:
348
- The dot product of two vectors (A and B) is given by the formula: dot_product = A.x * B.x + A.y * B.y
349
-
350
- The method takes another vector (other) as input and returns the dot product value.
351
-
352
- Example usage is shown in the "Example" section above.
353
- """
354
74
  return self.x * other.x + self.y * other.y
355
75
 
356
76
  def normalize(self) -> "Vector2D":
357
- """
358
- # Vector Normalization
359
-
360
- ## Returns:
361
- Vector2D: A new vector with the same direction as the current vector but with a magnitude of 1.
362
-
363
- ## Raises:
364
- ValueError: If the magnitude of the current vector is zero (zero vector).
365
-
366
- ## Example:
367
- v = Vector2D(3, 4)
368
- normalized_v = v.normalize() # Normalize the vector (3, 4)
369
- print(normalized_v) # Output: (0.6, 0.8)
370
-
371
- ## Explanation:
372
- 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.
373
-
374
- The method first calculates the magnitude of the current vector using the 'magnitude' method.
375
-
376
- If the magnitude is zero (zero vector), a ValueError is raised, as normalization is not defined for zero vectors.
377
-
378
- The normalized vector is obtained by dividing each component of the current vector by its magnitude.
379
-
380
- The resulting normalized vector is returned.
381
-
382
- Example usage is shown in the "Example" section above.
383
- """
384
77
  mag = self.length()
385
78
  if mag == 0:
386
79
  return self
387
80
  return Vector2D(self.x / mag, self.y / mag)
388
81
 
389
82
  def projection(self, other:"float|int|Vector2D|V2|list|tuple") -> "Vector2D|V2":
390
- """
391
- # Vector Projection
392
-
393
- ## Parameters:
394
- other (float, int, Vector2D, V2, list, tuple): The vector onto which to project.
395
-
396
- ## Returns:
397
- Vector2D or V2: The projection of the current vector onto the 'other' vector.
398
-
399
- ## Raises:
400
- ValueError: If 'other' is a zero vector.
401
-
402
- ## Example:
403
- v1 = Vector2D(3, 4)
404
- v2 = Vector2D(1, 0)
405
- projection_v = v1.projection(v2) # Calculate the projection of v1 onto v2
406
- print(projection_v) # Output: (3.0, 0.0)
407
-
408
- ## Explanation:
409
- This method calculates the projection of the current vector onto the 'other' vector.
410
- The projection is a vector that represents the component of the current vector in the direction of the 'other' vector.
411
-
412
- If 'other' is not a Vector2D instance, it will be converted to one using the '__normalize__' method.
413
- The method first normalizes the 'other' vector using the '__normalize__' method of the vector.
414
-
415
- Next, it calculates the dot product of the current vector and the normalized 'other' vector using the 'dot_product' method.
416
- It also calculates the squared magnitude of the 'other' vector using the 'magnitude' method.
417
-
418
- If the magnitude of 'other' is zero (a zero vector), a ValueError is raised, as projection is not defined for zero vectors.
419
-
420
- The projection is then obtained by scaling the 'other' vector by the dot product divided by the squared magnitude.
421
-
422
- The resulting projection vector is returned.
423
-
424
- Example usage is shown in the "Example" section above.
425
- """
426
83
  other = self.__normalize__(other)
427
84
  dot_product = self.dot_product(other)
428
85
  magnitude_product = other.length() ** 2
@@ -431,90 +88,17 @@ class Vector2D:
431
88
  return other * (dot_product / magnitude_product)
432
89
 
433
90
  def reflection(self, normal:"float|int|Vector2D|V2|list|tuple") -> "Vector2D|V2":
434
- """
435
- # Vector Reflection
436
-
437
- ## Parameters:
438
- normal (float, int, Vector2D, V2, list, tuple): The normal vector representing the surface of reflection.
439
-
440
- ## Returns:
441
- Vector2D or V2: The reflected vector.
442
-
443
- ## Example:
444
- incident_vector = Vector2D(3, 4)
445
- normal_vector = Vector2D(1, 0)
446
- reflected_vector = incident_vector.reflection(normal_vector) # Calculate the reflection of the incident vector over the given normal
447
- print(reflected_vector) # Output: (-3.0, 4.0)
448
-
449
- ## Explanation:
450
- This method calculates the reflection of the current vector over the given normal vector.
451
- The normal vector represents the surface of reflection, and it should be normalized (unit vector).
452
-
453
- The method first normalizes the 'normal' vector using the '__normalize__' method of the vector.
454
- Next, it calculates the projection of the current vector onto the 'normal' vector using the 'projection' method.
455
- The reflected vector is obtained by subtracting twice the projection from the current vector.
456
-
457
- The resulting reflected vector is returned.
458
-
459
- Example usage is shown in the "Example" section above.
460
- """
461
91
  normal = self.__normalize__(normal)
462
92
  projection = self.projection(normal)
463
93
  return self - projection * 2
464
94
 
465
95
  def cartesian_to_polar(self) -> tuple:
466
- """
467
- # Convert Cartesian Coordinates to Polar Coordinates
468
-
469
- ## Returns:
470
- tuple: A tuple containing the radial distance (magnitude) 'r' and the angle 'theta' in radians.
471
-
472
- ## Example:
473
- v = Vector2D(3, 4)
474
- r, theta = v.cartesian_to_polar() # Convert Cartesian coordinates (3, 4) to polar
475
- print(r, theta) # Output: (5.0, 0.9272952180016122)
476
-
477
- ## Explanation:
478
- This method converts Cartesian coordinates (x, y) to polar coordinates (r, theta).
479
- 'r' is the radial distance (magnitude) from the origin to the point, and 'theta' is the angle
480
- (in radians) measured from the positive x-axis to the point.
481
-
482
- The method calculates the radial distance 'r' using the 'magnitude' method of the vector.
483
- The angle 'theta' is calculated using the arctan2 function, which takes the y and x components of the vector.
484
-
485
- The resulting 'r' and 'theta' are returned as a tuple.
486
-
487
- Example usage is shown in the "Example" section above.
488
- """
489
96
  r = self.length()
490
97
  theta = _mt.atan2(self.y, self.x)
491
98
  return r, theta
492
99
 
493
100
  @classmethod
494
101
  def polar_to_cartesian(cls, r: float|int, theta: float|int) -> "Vector2D|V2":
495
- """
496
- # Convert Polar Coordinates to Cartesian Coordinates
497
-
498
- ## Parameters:
499
- r (float or int): The radial distance (magnitude) from the origin to the point.
500
- theta (float or int): The angle (in radians or degrees) measured from the positive x-axis to the point.
501
-
502
- ## Returns:
503
- Vector2D or V2: A new vector representing the Cartesian coordinates (x, y) of the point.
504
-
505
- ## Example:
506
- cartesian_point = Vector2D.polar_to_cartesian(5, math.pi/4) # Convert polar coordinates (r=5, theta=45 degrees) to Cartesian
507
- print(cartesian_point) # Output: (3.5355339059327378, 3.5355339059327373)
508
-
509
- ## Explanation:
510
- This class method converts polar coordinates (r, theta) to Cartesian coordinates (x, y).
511
- 'r' is the radial distance (magnitude) from the origin to the point, and 'theta' is the angle
512
- (in radians or degrees) measured from the positive x-axis to the point.
513
-
514
- The method calculates the x and y components using trigonometric functions (cosine and sine) based on 'r' and 'theta'.
515
-
516
- Example usage is shown in the "Example" section above.
517
- """
518
102
  x = r * _mt.cos(theta)
519
103
  y = r * _mt.sin(theta)
520
104
  return cls(x, y)
@@ -530,74 +114,12 @@ class Vector2D:
530
114
  return (self.x ** 2 + self.y ** 2) ** .5
531
115
 
532
116
  def lerp(self, other:"float|int|Vector2D|V2|list|tuple", t: float) -> "Vector2D|V2":
533
- """
534
- # Linear Interpolation (LERP)
535
-
536
- ## Parameters:
537
- other (float, int, Vector2D, V2, list, tuple): The vector to interpolate towards.
538
- t (float): The interpolation parameter. Must be between 0 and 1.
539
-
540
- ## Returns:
541
- Vector2D or V2: The result of the linear interpolation.
542
-
543
- ## Raises:
544
- ValueError: If t is not within the range [0, 1].
545
-
546
- ## Example:
547
- v1 = Vector2D(1, 2)
548
- v2 = Vector2D(5, 7)
549
- interpolated_v = v1.lerp(v2, 0.5) # Linearly interpolate between v1 and v2 with t = 0.5
550
- print(interpolated_v) # Output: (3.0, 4.5)
551
-
552
- ## Explanation:
553
- This method performs linear interpolation between the current vector and the 'other' vector.
554
- The 't' parameter represents the interpolation parameter, which controls how much the interpolation
555
- leans towards the 'other' vector. When 't' is 0, the result will be equal to the current vector (self).
556
- When 't' is 1, the result will be equal to the 'other' vector. For intermediate values of 't', the
557
- result will be a linear combination of the two vectors, smoothly transitioning between them.
558
-
559
- If 'other' is not a Vector2D instance, it will be converted to one using the '__normalize__' method.
560
- If 't' is not within the range [0, 1], a ValueError is raised.
561
-
562
- Example usage is shown in the "Example" section above.
563
- """
564
117
  other = self.__normalize__(other)
565
118
  if not 0 <= t <= 1:
566
119
  raise ValueError("t must be between 0 and 1 for linear interpolation.")
567
120
  return Vector2D(self.x + (other.x - self.x) * t, self.y + (other.y - self.y) * t)
568
121
 
569
122
  def rotate(self, angle: int|float, center:"float|int|Vector2D|V2|list|tuple|None"=None) -> "Vector2D|V2":
570
- """
571
- # Rotate the vector by a given angle around the origin or a specified center.
572
-
573
- ## Parameters:
574
- angle (int or float): The angle of rotation in radians or degrees, depending on the trigonometric functions used.
575
- center (float, int, Vector2D, V2, list, tuple, or None): The center of rotation.
576
- If None, the vector is rotated around the origin (0, 0).
577
-
578
- ## Returns:
579
- Vector2D or V2: The rotated vector.
580
-
581
- ## Example:
582
- v = Vector2D(3, 4)
583
- rotated_v = v.rotate(math.pi / 4) # Rotate 45 degrees around the origin
584
- print(rotated_v) # Output: (0.7071067811865476, 5.656854249492381)
585
-
586
- center = Vector2D(1, 1)
587
- rotated_v = v.rotate(math.pi / 4, center) # Rotate 45 degrees around the center (1, 1)
588
- print(rotated_v) # Output: (1.7071067811865475, 2.656854249492381)
589
-
590
- ## Explanation:
591
- This method rotates the vector by the specified angle around the given center.
592
- If no center is provided, the vector is rotated around the origin (0, 0).
593
-
594
- The method calculates the trigonometric functions (cosine and sine) of the angle to perform the rotation.
595
- The translated vector is obtained by subtracting the center from the current vector.
596
- The rotated vector is then obtained by applying the rotation transformation to the translated vector.
597
- The center is added back to the rotated vector to obtain the final result.
598
-
599
- Example usage is shown in the "Example" section above.
600
- """
601
123
  if center is None: center = V2z
602
124
  else: center = self.__normalize__(center)
603
125
  translated = self - center
@@ -606,48 +128,6 @@ class Vector2D:
606
128
  return Vector2D(translated.x * cos_angle - translated.y * sin_angle, translated.x * sin_angle + translated.y * cos_angle) + center
607
129
 
608
130
  def no_zero_div_error(self:"Vector2D|V2", n:"int|float|Vector2D|V2", error_mode:str="zero") -> "Vector2D|V2":
609
- """
610
- # Handle division between the Vector2D other and a numeric value or another Vector2D other.
611
-
612
- ## Parameters:
613
- n (int|float or Vector2D): The numeric value or Vector2D other for division.
614
- error_mode (str, optional): The mode to handle division by zero scenarios.
615
- - "zero" (default): Return a Vector2D other with zeros for both components.
616
- - "null": Return a Vector2D other with the original x or y component if available,
617
- otherwise, return NaN (Not a Number) for the component.
618
-
619
- ## Returns:
620
- Vector2D: A new Vector2D other after division or handling division by zero scenarios.
621
-
622
- ## Example:
623
- vector1 = Vector2D(3, 4)
624
-
625
- result1 = vector1.no_zero_div_error(2)
626
-
627
- print(result1.x, result1.y)
628
-
629
- vector2 = Vector2D(5, 0)
630
-
631
- result2 = vector1.no_zero_div_error(vector2, error_mode="null")
632
-
633
- print(result2.x, result2.y)
634
-
635
- ## Explanation:
636
- The function handles division between the Vector2D other and a numeric value or another Vector2D other.
637
-
638
- If n is a numeric value (int or float):
639
- - If n is zero, the function returns a Vector2D other with zeros for both components if error_mode is "zero".
640
- - If error_mode is "null", the function returns a Vector2D other with the original x or y component if available,
641
- otherwise, return NaN (Not a Number) for the component.
642
-
643
- If n is a Vector2D other:
644
- - If n's x or y component is zero, the function returns a Vector2D other with zeros for the corresponding component
645
- if error_mode is "zero".
646
- - If error_mode is "null", the function returns a Vector2D other with the original x or y component if available,
647
- otherwise, return NaN (Not a Number) for the component.
648
-
649
- If n is neither a numeric value nor a Vector2D other, the function raises an exception.
650
- """
651
131
  if any(isinstance(n, cls) for cls in {int, float}):
652
132
  if n == 0:
653
133
  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))
@@ -833,30 +313,13 @@ class Vector2D:
833
313
  raise TypeError(f"The value {other} is not a num type: [{int|float}] nor an array type: [{list|tuple}]")
834
314
  return other
835
315
 
316
+ try:
317
+ from e2D.Cmain import * #type: ignore
318
+ except Exception as err:
319
+ raise Warning(f"Unable to load the C-version on Vector2D: \n\t{err}")
320
+
836
321
  class V2(Vector2D):
837
322
  def __init__(self:"V2|Vector2D", x: int|float = 0, y: int|float = 0) -> None:
838
- """
839
- # Initialize a 2D vector (V2) with the specified x and y components.
840
-
841
- ## Parameters:
842
- x (int | float, optional): The x-component of the vector. Default is 0.
843
- y (int | float, optional): The y-component of the vector. Default is 0.
844
-
845
- ## Example:
846
- vector1 = V2() # Creates a V2 other with x=0 and y=0
847
- vector2 = V2(3, -2.5) # Creates a V2 other with x=3 and y=-2.5
848
-
849
- ## Explanation:
850
- This class is an alias for the Vector2D class, with the benefit of using a shorter name (V2).
851
-
852
- The constructor initializes a V2 other with the specified x and y components.
853
-
854
- If no arguments are provided, the default values for x and y are both set to 0.
855
-
856
- The x and y components can be integers or floating-point numbers.
857
-
858
- Example usage is shown in the "Example" section above.
859
- """
860
323
  super().__init__(x, y)
861
324
 
862
325
  V2inf = Vector2D(float('inf'), float('inf'))
@@ -1041,6 +504,12 @@ def angular_interpolation(starting_angle:int|float, final_angle:int|float, step:
1041
504
  distances = (final_angle - starting_angle, final_angle - DOUBLE_PI - starting_angle, final_angle + DOUBLE_PI - starting_angle)
1042
505
  return min(distances, key=abs) * step
1043
506
 
507
+ def bezier_cubic_interpolation(t:float, p0:Vector2D|V2, p1:Vector2D|V2) -> float:
508
+ return t*p0.y*3*(1 - t)**2 + p1.y*3*(1 - t) * t**2 + t**3
509
+
510
+ def bezier_quadratic_interpolation(t:float, p0:Vector2D|V2) -> float:
511
+ return 2*(1-t)*t*p0.y+t**2
512
+
1044
513
  def avg_position(*others:"Vector2D|V2") -> Vector2D|V2:
1045
514
  """
1046
515
  # Calculate the average position for a variable number of Vector2D others.
@@ -1,22 +1,93 @@
1
1
  from __future__ import annotations
2
2
  from .envs import *
3
3
  import numpy as np
4
- import ctypes
5
4
 
6
5
  class Function:
7
6
  def __init__(self) -> None:
8
7
  self.plot : Plot
9
8
  self.__layer_surface__ :pg.Surface= None #type: ignore
10
9
 
10
+ def __post_load_init__(self, plot:Plot) -> None:
11
+ self.plot = plot
12
+ self.__layer_surface__ = pg.Surface(self.plot.size(), pg.SRCALPHA, 32).convert_alpha()
13
+ self.update()
14
+ self.plot.functions.append(self)
15
+
11
16
  def update(self) -> None: pass
12
17
 
13
- def render(self) -> None: pass
18
+ def __render__(self) -> None: pass
14
19
 
15
20
  def draw(self) -> None:
16
21
  self.plot.canvas.blit(self.__layer_surface__, (0,0))
17
22
 
23
+ class Object:
24
+ def __init__(self) -> None:
25
+ self.plot : Plot
26
+ self.__layer_surface__ :pg.Surface= None #type: ignore
27
+ self.__controller__ = None
28
+
29
+ def __post_load_init__(self, plot:Plot, controller:Plot|Object) -> None:
30
+ self.plot = plot
31
+ self.__layer_surface__ = pg.Surface(self.plot.size(), pg.SRCALPHA, 32).convert_alpha()
32
+ self.__controller__ = controller
33
+ self.plot.objects.append(self)
34
+ if isinstance(self, Line):
35
+ self.point_a.__post_load_init__(self.plot, self)
36
+ self.point_b.__post_load_init__(self.plot, self)
37
+
38
+ def update(self) -> None: pass
39
+
40
+ def __render__(self) -> None: pass
41
+
42
+ def draw(self) -> None:
43
+ self.plot.canvas.blit(self.__layer_surface__, (0,0))
44
+
45
+ class Line(Object):
46
+ def __init__(self, point_a:V2|Vector2D|Point, point_b:V2|Vector2D|Point, color:list[float]|tuple[float,float,float]=(255,255,255), width:float=1) -> None:
47
+ super().__init__()
48
+ if isinstance(point_a, Point):
49
+ self.point_a = point_a
50
+ else:
51
+ self.point_a = Point(point_a)
52
+ if isinstance(point_b, Point):
53
+ self.point_b = point_b
54
+ else:
55
+ self.point_b = Point(point_b)
56
+ self.color = color
57
+ self.width = width
58
+
59
+ def update(self) -> None:
60
+ self.__render__()
61
+
62
+ def __render__(self) -> None:
63
+ self.__layer_surface__.fill((0,0,0,0))
64
+ if self.point_a.__controller__ == self: self.point_a.update()
65
+ if self.point_b.__controller__ == self: self.point_b.update()
66
+ pg.draw.line(self.__layer_surface__, self.color, self.point_a.center(), self.point_b.center(), self.width)
67
+
68
+ class Point(Object):
69
+ def __init__(self, position, label:str="", radius:float=1, color:list[float]|tuple[float,float,float]=(255,255,255)) -> None:
70
+ super().__init__()
71
+ self.position = position
72
+ self.radius = radius
73
+ self.color = color
74
+ self.label = label
75
+ self.rect :list[float]= [0, 0, 0, 0]
76
+ self.center = V2z.copy()
77
+
78
+ def update(self) -> None:
79
+ radius = self.radius * self.plot.size / (self.plot.bottom_right_plot_coord - self.plot.top_left_plot_coord) * self.plot.__y_axis_multiplier__
80
+ self.center = self.plot.__plot2real__(self.position)
81
+ position = self.center - radius * .5
82
+ self.rect = position() + radius()
83
+ self.__render__()
84
+
85
+ def __render__(self) -> None:
86
+ self.__layer_surface__.fill((0,0,0,0))
87
+ pg.draw.ellipse(self.__layer_surface__, self.color, self.rect)
88
+
18
89
  class MathFunction(Function):
19
- def __init__(self, function, color:list[float]|tuple[float,float,float]=(255,255,255), domain:list[float]=[-np.inf, np.inf], codomain:list[float]=[-np.inf, np.inf]) -> None:
90
+ def __init__(self, function, domain:list[float]=[-np.inf, np.inf], codomain:list[float]=[-np.inf, np.inf], color:list[float]|tuple[float,float,float]=(255,255,255)) -> None:
20
91
  super().__init__()
21
92
  self.color = color
22
93
  self.function = function
@@ -29,8 +100,6 @@ class MathFunction(Function):
29
100
  # credits for the plotting idea:
30
101
  # https://www.youtube.com/watch?v=EvvWOaLgKVU
31
102
  # mattbatwings (https://www.youtube.com/@mattbatwings)
32
-
33
- # real_domain = self.plot.__plot2real__()
34
103
  domain = [((domain - self.plot.top_left_plot_coord.x) * self.plot.size.x / (self.plot.bottom_right_plot_coord.x - self.plot.top_left_plot_coord.x)) for domain in self.domain]
35
104
  codomain = [((codomain - self.plot.top_left_plot_coord.y) * self.plot.size.y / (self.plot.bottom_right_plot_coord.y - self.plot.top_left_plot_coord.y)) for codomain in self.codomain]
36
105
 
@@ -48,12 +117,12 @@ class MathFunction(Function):
48
117
  if codomain != None: self.codomain = codomain
49
118
  if render:
50
119
  self.points = self.get_points()
51
- self.render()
120
+ self.__render__()
52
121
 
53
122
  def get_derivative(self, delta:float=.01, color:None|list[float]|tuple[float,float,float]=None) -> MathFunction:
54
- return MathFunction(lambda x,y: (self.function(x + delta, y) - self.function(x,y))/delta - y, color if color != None else self.color)
123
+ return MathFunction(lambda x,y: (self.function(x + delta, y) - self.function(x,y))/delta - y, color if color != None else self.color) #type: ignore
55
124
 
56
- def render(self) -> None:
125
+ def __render__(self) -> None:
57
126
  self.__layer_surface__.fill((0,0,0,0))
58
127
  offset = self.plot.dragging - self.plot.start_dragging if (self.plot.dragging != None) and (not self.plot.settings.get("use_real_time_rendering")) else V2z
59
128
  if any(x < 1 for x in self.plot.scale):
@@ -67,7 +136,47 @@ class MathFunction(Function):
67
136
  if self.plot.dragging != None:
68
137
  point = round(point + offset)()
69
138
  self.__layer_surface__.set_at(point, self.color) #type: ignore
70
-
139
+
140
+ class TimeFunction(Function):
141
+ def __init__(self, function, t_range:list[float]=[0,0, 1.0], t_step:float=.01, color:list[float]|tuple[float,float,float]=(255,255,255)) -> None:
142
+ super().__init__()
143
+ self.color = color
144
+ self.function = function
145
+ self.t_range = t_range
146
+ self.t_step = t_step
147
+ def get_points(self) -> list:
148
+ signs_self = np.sign(self.function(*self.plot.meshgrid))
149
+ domain = [((domain - self.plot.top_left_plot_coord.x) * self.plot.size.x / (self.plot.bottom_right_plot_coord.x - self.plot.top_left_plot_coord.x)) for domain in self.domain]
150
+ codomain = [((codomain - self.plot.top_left_plot_coord.y) * self.plot.size.y / (self.plot.bottom_right_plot_coord.y - self.plot.top_left_plot_coord.y)) for codomain in self.codomain]
151
+ signs_sum = signs_self + np.roll(signs_self, axis=1, shift=1) + np.roll(signs_self, axis=0, shift=-1) + np.roll(signs_self, axis=(1,0), shift=(1,-1))
152
+ coords = np.column_stack(np.where(((-4 < signs_sum) & (signs_sum < 4))[:-1, 1:])[::-1]) / self.plot.scale()
153
+ return coords[
154
+ np.logical_and(
155
+ np.logical_and(coords[:, 0] >= domain[0], coords[:, 0] <= domain[1]),
156
+ np.logical_and(coords[:, 1] >= codomain[0], coords[:, 1] <= codomain[1]))] #type: ignore
157
+ def update(self, new_function=None, render=True, domain:list[float]|None=None, codomain:list[float]|None=None) -> None:
158
+ if new_function != None:
159
+ self.function = new_function
160
+ if domain != None: self.domain = domain
161
+ if codomain != None: self.codomain = codomain
162
+ if render:
163
+ self.points = self.get_points()
164
+ self.__render__()
165
+ def __render__(self) -> None:
166
+ self.__layer_surface__.fill((0,0,0,0))
167
+ offset = self.plot.dragging - self.plot.start_dragging if (self.plot.dragging != None) and (not self.plot.settings.get("use_real_time_rendering")) else V2z
168
+ if any(x < 1 for x in self.plot.scale):
169
+ # draw rects
170
+ for point in self.points:
171
+ pg.draw.rect(self.__layer_surface__, self.color, (point.tolist() + offset)() + self.plot.pixel_size()) #type: ignore
172
+ else:
173
+ # draw points
174
+ for point in self.points:
175
+ point = point.astype(int).tolist()
176
+ if self.plot.dragging != None:
177
+ point = round(point + offset)()
178
+ self.__layer_surface__.set_at(point, self.color) #type: ignore
179
+
71
180
  class PointsFunction(Function):
72
181
  def __init__(self, points:list[V2|Vector2D]=[], points_color:list[float]|tuple[float,float,float]=(255,0,0), color:list[float]|tuple[float,float,float]=(255,255,255)) -> None:
73
182
  super().__init__()
@@ -80,9 +189,9 @@ class PointsFunction(Function):
80
189
  self.plot_points = [self.plot.__plot2real__(point)() for point in self.points if \
81
190
  self.plot.top_left_x < point.x < self.plot.bottom_right_x and \
82
191
  self.plot.bottom_right_y < point.y < self.plot.top_left_y]
83
- self.render()
192
+ self.__render__()
84
193
 
85
- def render(self) -> None:
194
+ def __render__(self) -> None:
86
195
  self.__layer_surface__.fill((0,0,0,0))
87
196
  if len(self.plot_points)>=2: pg.draw.lines(self.__layer_surface__, self.color, False, self.plot_points) #type: ignore
88
197
  # for point in self.points:
@@ -225,30 +334,25 @@ class Plot:
225
334
  __y_axis_multiplier__ = V2(1, -1)
226
335
  def __init__(self, rootEnv:"RootEnv", plot_position:V2|Vector2D, plot_size:V2|Vector2D, top_left_plot_coord:V2|Vector2D, bottom_right_plot_coord: V2|Vector2D, scale:V2|Vector2D=V2one) -> None:
227
336
  self.rootEnv = rootEnv
228
-
229
337
  self.top_left_plot_coord = top_left_plot_coord
230
338
  self.bottom_right_plot_coord = bottom_right_plot_coord
231
-
232
339
  self.position = plot_position
233
340
  self.size = plot_size
234
341
  self.scale = scale
235
-
236
342
  self.settings = __PlotSettings__(self)
237
343
  self.functions :list[Function]= []
238
-
344
+ self.objects :list[Object]= []
239
345
  self.canvas = pg.Surface(self.size(), pg.SRCALPHA, 32).convert_alpha()
240
346
  self.dragging = None
241
347
  self.start_dragging = V2z
242
348
  self.is_mouse_in_rect = False
243
349
  self.mouse_scalar = V2one.copy()
244
-
245
350
  self.plot_mouse_position = V2z.copy()
246
-
247
- self.focus(V2(0,0), 10)
351
+ self.focus_using_corners(top_left_plot_coord, bottom_right_plot_coord)
248
352
 
249
353
  def set_borders_by_position_and_zoom(self) -> None:
250
- self.top_left_plot_coord = self.current_offset - (.5**(.1*self.current_zoom)) * self.__y_axis_multiplier__
251
- self.bottom_right_plot_coord = self.current_offset + (.5**(.1*self.current_zoom)) * self.__y_axis_multiplier__
354
+ self.top_left_plot_coord = self.current_offset - 2**(-.1*self.current_zoom) * self.__y_axis_multiplier__
355
+ self.bottom_right_plot_coord = self.current_offset + 2**(-.1*self.current_zoom) * self.__y_axis_multiplier__
252
356
  self.top_left_x, self.top_left_y = self.top_left_plot_coord
253
357
  self.bottom_right_x, self.bottom_right_y = self.bottom_right_plot_coord
254
358
 
@@ -263,13 +367,21 @@ class Plot:
263
367
  self.pixel_size += V2one
264
368
 
265
369
  def load_function(self, function:Function) -> None:
266
- function.plot = self
267
- function.__layer_surface__ = pg.Surface(self.size(), pg.SRCALPHA, 32).convert_alpha()
268
- function.update()
269
- self.functions.append(function)
270
-
370
+ function.__post_load_init__(self)
271
371
  def add_function(self, function:Function) -> None:
272
372
  self.load_function(function)
373
+
374
+ def load_object(self, obj:Object) -> None:
375
+ obj.__post_load_init__(self, self)
376
+ def add_object(self, function:Object) -> None:
377
+ self.load_object(function)
378
+
379
+ def add(self, *data:Object|Function) -> None:
380
+ for d in data:
381
+ if isinstance(d, Object):
382
+ self.load_object(d)
383
+ elif isinstance(d, Function):
384
+ self.load_function(d)
273
385
 
274
386
  def __plot2real__(self, plot_position:V2|Vector2D) -> V2|Vector2D:
275
387
  return (plot_position + self.top_left_plot_coord * -1) * self.size / (self.bottom_right_plot_coord - self.top_left_plot_coord)
@@ -295,6 +407,8 @@ class Plot:
295
407
 
296
408
  # draw functions
297
409
  for function in self.functions: function.draw()
410
+ # draw objects
411
+ for obj in self.objects: obj.draw()
298
412
 
299
413
  # draw rect, pointer and corner coords
300
414
  if self.settings.get("draw_rect"):
@@ -378,18 +492,28 @@ class Plot:
378
492
  self.update_grid()
379
493
  self.render()
380
494
 
495
+ def get_humanoid_zoom(self) -> None:
496
+ return 2 ** (-.1*self.current_zoom)
497
+
381
498
  def focus(self, center:V2|Vector2D|None=None, zoom:float|Vector2D|V2|None=None) -> None:
382
499
  if center != None:
383
500
  self.current_offset = center.copy()
384
501
  if zoom != None:
385
502
  if any(isinstance(zoom, cls) for cls in {Vector2D, V2}):
386
- self.current_zoom = 0-V2(np.log2(zoom.x), np.log2(zoom.y)) * 10
503
+ self.current_zoom = V2(np.log2(zoom.x), np.log2(zoom.y)) * -10
387
504
  else:
388
505
  self.current_zoom = V2one * -np.log2(zoom)*10
389
506
 
390
507
  self.update_grid(True)
391
508
  for function in self.functions: function.update()
509
+ for obj in self.objects: obj.update()
392
510
  self.render()
511
+
512
+ def focus_using_corners(self, top_left_plot_coord:V2|Vector2D|None=None, bottom_right_plot_coord: V2|Vector2D|None=None) -> None:
513
+ self.focus(
514
+ (top_left_plot_coord + bottom_right_plot_coord)/2,
515
+ (bottom_right_plot_coord - top_left_plot_coord)/2 * self.__y_axis_multiplier__
516
+ )
393
517
 
394
518
  def draw(self) -> None:
395
519
  # fill canvas with bg color
@@ -1,7 +1,7 @@
1
1
  from e2D.envs import *
2
2
  import pygame as pg
3
- import cv2
4
3
  import numpy as np
4
+ import cv2
5
5
 
6
6
  class WinRec:
7
7
  def __init__(self, rootEnv:RootEnv, fps:int=30, path:str='output.mp4') -> None:
@@ -17,9 +17,14 @@ class WinRec:
17
17
  def update(self) -> None:
18
18
  frame = cv2.cvtColor(np.swapaxes(pg.surfarray.array3d(self.rootEnv.screen), 0, 1), cv2.COLOR_RGB2BGR)
19
19
  self.video_writer.write(frame)
20
+
21
+ def get_rec_seconds(self) -> float:
22
+ return self.rootEnv.current_frame/self.fps
20
23
 
21
- def draw(self) -> None:
22
- self.rootEnv.print(f"[cfps:{self.rootEnv.current_frame} || realtime:{round(self.rootEnv.current_frame/self.fps,2)} || apptime:{round(self.rootEnv.get_time_from_start(),2)}]", self.rootEnv.screen_size, fixed_sides=TEXT_FIXED_SIDES_BOTTOM_RIGHT)
24
+ def draw(self, draw_on_screen=False) -> None:
25
+ text = f"[cfps:{self.rootEnv.current_frame} || realtime:{round(self.get_rec_seconds(),2)} || apptime:{round(self.rootEnv.get_time_from_start(),2)}]"
26
+ pg.display.set_caption(text)
27
+ if draw_on_screen: self.rootEnv.print(text, self.rootEnv.screen_size, fixed_sides=TEXT_FIXED_SIDES_BOTTOM_RIGHT)
23
28
 
24
29
  def quit(self) -> None:
25
30
  self.video_writer.release()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: e2D
3
- Version: 1.4.0
3
+ Version: 1.4.2
4
4
  Summary: Python library for 2D games. Streamlines dev with keyboard/mouse input, vector calculations, color manipulation, and collision detection. Simplify game creation and unleash creativity!
5
5
  Home-page: https://github.com/marick-py/e2D
6
6
  Author: Riccardo Mariani
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = e2D
3
- version = 1.4.0
3
+ version = 1.4.2
4
4
  author = Riccardo Mariani
5
5
  author_email = ricomari2006@gmail.com
6
6
  description = Python library for 2D games. Streamlines dev with keyboard/mouse input, vector calculations, color manipulation, and collision detection. Simplify game creation and unleash creativity!
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes