0
# Mathematical Operations
1
2
Vector mathematics and geometric utilities for game development. Provides 2D and 3D vector operations, rectangle manipulation, and collision detection.
3
4
## Capabilities
5
6
### 2D Vector Mathematics
7
8
The Vector2 class provides comprehensive 2D vector operations for game mathematics.
9
10
```python { .api }
11
class Vector2:
12
def __init__(self, x: float = 0, y: float = 0):
13
"""
14
Initialize 2D vector.
15
16
Parameters:
17
x: X component
18
y: Y component
19
"""
20
21
x: float # X component
22
y: float # Y component
23
24
# Properties
25
length: float # Vector magnitude (read-only)
26
length_squared: float # Squared magnitude (read-only, faster than length)
27
magnitude: float # Alias for length
28
magnitude_squared: float # Alias for length_squared
29
30
def normalize(self) -> Vector2:
31
"""
32
Get normalized vector (length 1).
33
34
Returns:
35
Vector2: New normalized vector
36
"""
37
38
def normalize_ip(self) -> None:
39
"""Normalize vector in place."""
40
41
def is_normalized(self, tolerance: float = 1e-6) -> bool:
42
"""
43
Check if vector is normalized.
44
45
Parameters:
46
tolerance: Tolerance for comparison
47
48
Returns:
49
bool: True if vector length is approximately 1
50
"""
51
52
def scale_to_length(self, length: float) -> Vector2:
53
"""
54
Scale vector to specific length.
55
56
Parameters:
57
length: Target length
58
59
Returns:
60
Vector2: New vector with specified length
61
"""
62
63
def distance_to(self, other: Vector2) -> float:
64
"""
65
Calculate distance to another vector.
66
67
Parameters:
68
other: Other vector
69
70
Returns:
71
float: Distance between vectors
72
"""
73
74
def distance_squared_to(self, other: Vector2) -> float:
75
"""
76
Calculate squared distance (faster than distance_to).
77
78
Parameters:
79
other: Other vector
80
81
Returns:
82
float: Squared distance
83
"""
84
85
def dot(self, other: Vector2) -> float:
86
"""
87
Calculate dot product.
88
89
Parameters:
90
other: Other vector
91
92
Returns:
93
float: Dot product
94
"""
95
96
def cross(self, other: Vector2) -> float:
97
"""
98
Calculate cross product (returns scalar in 2D).
99
100
Parameters:
101
other: Other vector
102
103
Returns:
104
float: Cross product magnitude
105
"""
106
107
def angle_to(self, other: Vector2) -> float:
108
"""
109
Calculate angle between vectors in degrees.
110
111
Parameters:
112
other: Other vector
113
114
Returns:
115
float: Angle in degrees
116
"""
117
118
def rotate(self, angle: float) -> Vector2:
119
"""
120
Rotate vector by angle in degrees.
121
122
Parameters:
123
angle: Rotation angle in degrees
124
125
Returns:
126
Vector2: New rotated vector
127
"""
128
129
def rotate_ip(self, angle: float) -> None:
130
"""
131
Rotate vector in place.
132
133
Parameters:
134
angle: Rotation angle in degrees
135
"""
136
137
def rotate_rad(self, angle: float) -> Vector2:
138
"""
139
Rotate vector by angle in radians.
140
141
Parameters:
142
angle: Rotation angle in radians
143
144
Returns:
145
Vector2: New rotated vector
146
"""
147
148
def rotate_rad_ip(self, angle: float) -> None:
149
"""
150
Rotate vector in place by radians.
151
152
Parameters:
153
angle: Rotation angle in radians
154
"""
155
156
def reflect(self, normal: Vector2) -> Vector2:
157
"""
158
Reflect vector across normal.
159
160
Parameters:
161
normal: Surface normal vector
162
163
Returns:
164
Vector2: Reflected vector
165
"""
166
167
def reflect_ip(self, normal: Vector2) -> None:
168
"""
169
Reflect vector in place.
170
171
Parameters:
172
normal: Surface normal vector
173
"""
174
175
def lerp(self, other: Vector2, t: float) -> Vector2:
176
"""
177
Linear interpolation between vectors.
178
179
Parameters:
180
other: Target vector
181
t: Interpolation factor (0.0 to 1.0)
182
183
Returns:
184
Vector2: Interpolated vector
185
"""
186
187
def slerp(self, other: Vector2, t: float) -> Vector2:
188
"""
189
Spherical linear interpolation.
190
191
Parameters:
192
other: Target vector
193
t: Interpolation factor (0.0 to 1.0)
194
195
Returns:
196
Vector2: Interpolated vector
197
"""
198
199
def move_towards(self, target: Vector2, max_distance: float) -> Vector2:
200
"""
201
Move towards target by maximum distance.
202
203
Parameters:
204
target: Target vector
205
max_distance: Maximum distance to move
206
207
Returns:
208
Vector2: New vector moved towards target
209
"""
210
211
def move_towards_ip(self, target: Vector2, max_distance: float) -> None:
212
"""
213
Move towards target in place.
214
215
Parameters:
216
target: Target vector
217
max_distance: Maximum distance to move
218
"""
219
220
def clamp_magnitude(self, max_length: float) -> Vector2:
221
"""
222
Clamp vector magnitude to maximum length.
223
224
Parameters:
225
max_length: Maximum allowed length
226
227
Returns:
228
Vector2: Clamped vector
229
"""
230
231
def clamp_magnitude_ip(self, max_length: float) -> None:
232
"""
233
Clamp magnitude in place.
234
235
Parameters:
236
max_length: Maximum allowed length
237
"""
238
239
def copy(self) -> Vector2:
240
"""
241
Create copy of vector.
242
243
Returns:
244
Vector2: Vector copy
245
"""
246
247
def as_polar(self) -> tuple[float, float]:
248
"""
249
Convert to polar coordinates.
250
251
Returns:
252
tuple[float, float]: (radius, angle_degrees)
253
"""
254
255
@classmethod
256
def from_polar(cls, polar: tuple[float, float]) -> Vector2:
257
"""
258
Create vector from polar coordinates.
259
260
Parameters:
261
polar: (radius, angle_degrees)
262
263
Returns:
264
Vector2: New vector from polar coordinates
265
"""
266
267
def elementwise(self) -> VectorElementwiseProxy:
268
"""
269
Get elementwise operations proxy.
270
271
Returns:
272
VectorElementwiseProxy: Proxy for elementwise operations
273
"""
274
```
275
276
### 3D Vector Mathematics
277
278
The Vector3 class extends vector operations to three dimensions.
279
280
```python { .api }
281
class Vector3:
282
def __init__(self, x: float = 0, y: float = 0, z: float = 0):
283
"""
284
Initialize 3D vector.
285
286
Parameters:
287
x: X component
288
y: Y component
289
z: Z component
290
"""
291
292
x: float # X component
293
y: float # Y component
294
z: float # Z component
295
296
# Properties (similar to Vector2)
297
length: float
298
length_squared: float
299
magnitude: float
300
magnitude_squared: float
301
302
# Methods (similar to Vector2 with 3D extensions)
303
def normalize(self) -> Vector3: ...
304
def normalize_ip(self) -> None: ...
305
def scale_to_length(self, length: float) -> Vector3: ...
306
def distance_to(self, other: Vector3) -> float: ...
307
def distance_squared_to(self, other: Vector3) -> float: ...
308
def dot(self, other: Vector3) -> float: ...
309
310
def cross(self, other: Vector3) -> Vector3:
311
"""
312
Calculate cross product (returns Vector3 in 3D).
313
314
Parameters:
315
other: Other vector
316
317
Returns:
318
Vector3: Cross product vector
319
"""
320
321
def rotate_x(self, angle: float) -> Vector3:
322
"""
323
Rotate around X axis.
324
325
Parameters:
326
angle: Rotation angle in degrees
327
328
Returns:
329
Vector3: Rotated vector
330
"""
331
332
def rotate_x_ip(self, angle: float) -> None: ...
333
def rotate_y(self, angle: float) -> Vector3: ...
334
def rotate_y_ip(self, angle: float) -> None: ...
335
def rotate_z(self, angle: float) -> Vector3: ...
336
def rotate_z_ip(self, angle: float) -> None: ...
337
338
def reflect(self, normal: Vector3) -> Vector3: ...
339
def reflect_ip(self, normal: Vector3) -> None: ...
340
def lerp(self, other: Vector3, t: float) -> Vector3: ...
341
def slerp(self, other: Vector3, t: float) -> Vector3: ...
342
def copy(self) -> Vector3: ...
343
```
344
345
### Rectangle Mathematics
346
347
Enhanced rectangle operations for collision detection and positioning.
348
349
```python { .api }
350
class Rect:
351
def __init__(self, left: int, top: int, width: int, height: int):
352
"""
353
Initialize rectangle.
354
355
Parameters:
356
left: Left coordinate
357
top: Top coordinate
358
width: Rectangle width
359
height: Rectangle height
360
"""
361
362
# Position and size attributes
363
x: int # Left coordinate (alias for left)
364
y: int # Top coordinate (alias for top)
365
left: int # Left edge
366
top: int # Top edge
367
width: int # Width
368
height: int # Height
369
w: int # Width alias
370
h: int # Height alias
371
372
# Computed attributes
373
right: int # Right edge (left + width)
374
bottom: int # Bottom edge (top + height)
375
376
# Corner positions
377
topleft: tuple[int, int] # (left, top)
378
topright: tuple[int, int] # (right, top)
379
bottomleft: tuple[int, int] # (left, bottom)
380
bottomright: tuple[int, int] # (right, bottom)
381
382
# Edge midpoints
383
midtop: tuple[int, int] # (centerx, top)
384
midleft: tuple[int, int] # (left, centery)
385
midbottom: tuple[int, int] # (centerx, bottom)
386
midright: tuple[int, int] # (right, centery)
387
388
# Center
389
center: tuple[int, int] # (centerx, centery)
390
centerx: int # Horizontal center
391
centery: int # Vertical center
392
393
# Size
394
size: tuple[int, int] # (width, height)
395
396
def copy(self) -> Rect:
397
"""Create copy of rectangle."""
398
399
def move(self, x: int, y: int) -> Rect:
400
"""
401
Create moved rectangle.
402
403
Parameters:
404
x: Horizontal offset
405
y: Vertical offset
406
407
Returns:
408
Rect: New moved rectangle
409
"""
410
411
def move_ip(self, x: int, y: int) -> None:
412
"""Move rectangle in place."""
413
414
def inflate(self, x: int, y: int) -> Rect:
415
"""
416
Create inflated rectangle (grow/shrink).
417
418
Parameters:
419
x: Horizontal size change
420
y: Vertical size change
421
422
Returns:
423
Rect: New inflated rectangle
424
"""
425
426
def inflate_ip(self, x: int, y: int) -> None:
427
"""Inflate rectangle in place."""
428
429
def scale_by(self, scalar: float) -> Rect:
430
"""
431
Scale rectangle by factor.
432
433
Parameters:
434
scalar: Scale factor
435
436
Returns:
437
Rect: Scaled rectangle
438
"""
439
440
def scale_by_ip(self, scalar: float) -> None:
441
"""Scale rectangle in place."""
442
443
def clamp(self, rect: Rect) -> Rect:
444
"""
445
Create rectangle clamped inside another rectangle.
446
447
Parameters:
448
rect: Bounding rectangle
449
450
Returns:
451
Rect: Clamped rectangle
452
"""
453
454
def clamp_ip(self, rect: Rect) -> None:
455
"""Clamp rectangle in place."""
456
457
def clip(self, rect: Rect) -> Rect:
458
"""
459
Get intersection with another rectangle.
460
461
Parameters:
462
rect: Rectangle to intersect with
463
464
Returns:
465
Rect: Intersection rectangle (may have zero area)
466
"""
467
468
def clipline(self, line: tuple[tuple[int, int], tuple[int, int]]) -> tuple[tuple[int, int], tuple[int, int]] | tuple[]:
469
"""
470
Clip line to rectangle boundaries.
471
472
Parameters:
473
line: ((x1, y1), (x2, y2)) line endpoints
474
475
Returns:
476
tuple: Clipped line endpoints or empty tuple if no intersection
477
"""
478
479
def union(self, rect: Rect) -> Rect:
480
"""
481
Get union with another rectangle.
482
483
Parameters:
484
rect: Rectangle to union with
485
486
Returns:
487
Rect: Bounding rectangle containing both
488
"""
489
490
def union_ip(self, rect: Rect) -> None:
491
"""Union rectangle in place."""
492
493
def unionall(self, rect_sequence: list[Rect]) -> Rect:
494
"""
495
Union with multiple rectangles.
496
497
Parameters:
498
rect_sequence: List of rectangles
499
500
Returns:
501
Rect: Bounding rectangle containing all
502
"""
503
504
def unionall_ip(self, rect_sequence: list[Rect]) -> None:
505
"""Union all rectangles in place."""
506
507
def fit(self, rect: Rect) -> Rect:
508
"""
509
Fit rectangle inside another rectangle, maintaining aspect ratio.
510
511
Parameters:
512
rect: Container rectangle
513
514
Returns:
515
Rect: Fitted rectangle
516
"""
517
518
def normalize(self) -> None:
519
"""Ensure width and height are positive."""
520
521
def contains(self, rect: Rect) -> bool:
522
"""
523
Check if rectangle completely contains another.
524
525
Parameters:
526
rect: Rectangle to test
527
528
Returns:
529
bool: True if rect is completely inside this rectangle
530
"""
531
532
def collidepoint(self, point: tuple[int, int]) -> bool:
533
"""
534
Check if point is inside rectangle.
535
536
Parameters:
537
point: (x, y) point to test
538
539
Returns:
540
bool: True if point is inside
541
"""
542
543
def colliderect(self, rect: Rect) -> bool:
544
"""
545
Check if rectangles overlap.
546
547
Parameters:
548
rect: Rectangle to test
549
550
Returns:
551
bool: True if rectangles overlap
552
"""
553
554
def collidelist(self, list: list[Rect]) -> int:
555
"""
556
Find first colliding rectangle in list.
557
558
Parameters:
559
list: List of rectangles to test
560
561
Returns:
562
int: Index of first collision, or -1 if none
563
"""
564
565
def collidelistall(self, list: list[Rect]) -> list[int]:
566
"""
567
Find all colliding rectangles in list.
568
569
Parameters:
570
list: List of rectangles to test
571
572
Returns:
573
list[int]: Indices of all colliding rectangles
574
"""
575
576
def collidedict(self, dict: dict) -> tuple | None:
577
"""
578
Find first colliding rectangle in dictionary.
579
580
Parameters:
581
dict: Dictionary with Rect values
582
583
Returns:
584
tuple | None: (key, value) of first collision or None
585
"""
586
587
def collidedictall(self, dict: dict) -> list[tuple]:
588
"""
589
Find all colliding rectangles in dictionary.
590
591
Parameters:
592
dict: Dictionary with Rect values
593
594
Returns:
595
list[tuple]: List of (key, value) pairs for collisions
596
"""
597
598
def update(self, *args) -> None:
599
"""Update rectangle with new position/size."""
600
```
601
602
### Floating-Point Rectangles
603
604
FRect provides the same interface as Rect but with floating-point precision.
605
606
```python { .api }
607
class FRect:
608
"""Floating-point rectangle with same methods as Rect."""
609
610
def __init__(self, left: float, top: float, width: float, height: float): ...
611
612
# All attributes and methods same as Rect but with float types
613
x: float
614
y: float
615
width: float
616
height: float
617
# ... (same interface as Rect)
618
```
619
620
## Usage Examples
621
622
### Vector Mathematics
623
624
```python
625
import pygame
626
import math
627
628
# Create vectors
629
velocity = pygame.Vector2(5, 3)
630
position = pygame.Vector2(100, 100)
631
target = pygame.Vector2(400, 300)
632
633
# Basic operations
634
print(f"Velocity magnitude: {velocity.length}")
635
print(f"Distance to target: {position.distance_to(target)}")
636
637
# Normalize for direction
638
direction = (target - position).normalize()
639
print(f"Direction to target: {direction}")
640
641
# Move towards target
642
max_speed = 200 # pixels per second
643
dt = 1/60 # delta time (60 FPS)
644
new_position = position.move_towards(target, max_speed * dt)
645
646
# Rotation
647
velocity_rotated = velocity.rotate(45) # 45 degrees
648
print(f"Rotated velocity: {velocity_rotated}")
649
650
# Reflection (for bouncing)
651
wall_normal = pygame.Vector2(0, -1) # Upward normal
652
reflected_velocity = velocity.reflect(wall_normal)
653
654
# Dot product (for angle calculations)
655
angle = math.degrees(math.acos(velocity.normalize().dot(direction)))
656
print(f"Angle between velocity and direction: {angle}")
657
```
658
659
### Advanced Movement
660
661
```python
662
import pygame
663
import math
664
665
class MovingObject:
666
def __init__(self, x, y):
667
self.position = pygame.Vector2(x, y)
668
self.velocity = pygame.Vector2(0, 0)
669
self.acceleration = pygame.Vector2(0, 0)
670
self.max_speed = 200
671
self.friction = 0.95
672
673
def seek(self, target):
674
"""Steer towards target position"""
675
desired = (target - self.position).normalize() * self.max_speed
676
steer = desired - self.velocity
677
return steer.clamp_magnitude(5) # Max steering force
678
679
def update(self, dt):
680
# Apply physics
681
self.velocity += self.acceleration * dt
682
self.velocity *= self.friction # Apply friction
683
self.velocity = self.velocity.clamp_magnitude(self.max_speed)
684
self.position += self.velocity * dt
685
686
# Reset acceleration
687
self.acceleration *= 0
688
689
# Example usage
690
obj = MovingObject(100, 100)
691
target = pygame.Vector2(400, 300)
692
693
# Apply steering force
694
steering = obj.seek(target)
695
obj.acceleration += steering
696
697
obj.update(1/60) # Update with 60 FPS delta time
698
```
699
700
### Rectangle Collision Detection
701
702
```python
703
import pygame
704
705
# Create rectangles
706
player = pygame.Rect(100, 100, 50, 50)
707
wall = pygame.Rect(200, 150, 20, 100)
708
enemies = [
709
pygame.Rect(300, 120, 40, 40),
710
pygame.Rect(350, 180, 30, 30),
711
pygame.Rect(400, 200, 35, 35)
712
]
713
714
# Point collision
715
mouse_pos = pygame.mouse.get_pos()
716
if player.collidepoint(mouse_pos):
717
print("Mouse over player")
718
719
# Rectangle collision
720
if player.colliderect(wall):
721
print("Player hit wall")
722
723
# Multiple collision testing
724
hit_enemy = player.collidelist(enemies)
725
if hit_enemy >= 0:
726
print(f"Player hit enemy {hit_enemy}")
727
728
# Get all collisions
729
all_hits = player.collidelistall(enemies)
730
print(f"Player hit enemies: {all_hits}")
731
732
# Movement with collision
733
keys = pygame.key.get_pressed()
734
old_pos = player.topleft
735
736
if keys[pygame.K_LEFT]:
737
player.x -= 5
738
if keys[pygame.K_RIGHT]:
739
player.x += 5
740
if keys[pygame.K_UP]:
741
player.y -= 5
742
if keys[pygame.K_DOWN]:
743
player.y += 5
744
745
# Check for wall collision and revert if needed
746
if player.colliderect(wall):
747
player.topleft = old_pos # Revert movement
748
```
749
750
### Rectangle Manipulation
751
752
```python
753
import pygame
754
755
# Create rectangle
756
rect = pygame.Rect(100, 100, 200, 150)
757
758
# Position by different anchors
759
rect.center = (400, 300) # Center at point
760
rect.topleft = (50, 50) # Top-left corner
761
rect.bottomright = (800, 600) # Bottom-right corner
762
763
# Resize and move
764
larger_rect = rect.inflate(50, 30) # Grow by 50x30
765
moved_rect = rect.move(10, -5) # Move by offset
766
767
# Clamp inside screen
768
screen_rect = pygame.Rect(0, 0, 800, 600)
769
clamped_rect = rect.clamp(screen_rect)
770
771
# Get intersection
772
overlap = rect.clip(larger_rect)
773
if overlap.width > 0 and overlap.height > 0:
774
print(f"Overlap area: {overlap}")
775
776
# Union (bounding box)
777
bounding_box = rect.union(moved_rect)
778
print(f"Bounding box: {bounding_box}")
779
780
# Line clipping
781
line = ((0, 0), (800, 600)) # Diagonal line
782
clipped_line = rect.clipline(line)
783
if clipped_line:
784
print(f"Line enters/exits rect at: {clipped_line}")
785
```
786
787
### Game Physics Example
788
789
```python
790
import pygame
791
import math
792
793
class PhysicsObject:
794
def __init__(self, x, y, mass=1.0):
795
self.position = pygame.Vector2(x, y)
796
self.velocity = pygame.Vector2(0, 0)
797
self.acceleration = pygame.Vector2(0, 0)
798
self.mass = mass
799
self.radius = 20
800
801
def apply_force(self, force):
802
"""Apply force (F = ma, so a = F/m)"""
803
self.acceleration += force / self.mass
804
805
def update(self, dt):
806
# Integrate physics
807
self.velocity += self.acceleration * dt
808
self.position += self.velocity * dt
809
810
# Clear acceleration
811
self.acceleration *= 0
812
813
# Boundary collision (simple bounce)
814
if self.position.x < self.radius or self.position.x > 800 - self.radius:
815
self.velocity.x *= -0.8 # Bounce with energy loss
816
if self.position.y < self.radius or self.position.y > 600 - self.radius:
817
self.velocity.y *= -0.8
818
819
# Clamp position to screen
820
self.position.x = max(self.radius, min(800 - self.radius, self.position.x))
821
self.position.y = max(self.radius, min(600 - self.radius, self.position.y))
822
823
# Create objects
824
ball1 = PhysicsObject(200, 100, mass=2.0)
825
ball2 = PhysicsObject(600, 100, mass=1.0)
826
827
# Apply different forces
828
gravity = pygame.Vector2(0, 500) # Downward gravity
829
wind = pygame.Vector2(-100, 0) # Left wind
830
831
# Game loop
832
clock = pygame.time.Clock()
833
dt = 0
834
835
while True:
836
dt = clock.tick(60) / 1000.0 # Delta time in seconds
837
838
# Apply forces
839
ball1.apply_force(gravity * ball1.mass) # Weight = mg
840
ball1.apply_force(wind)
841
842
ball2.apply_force(gravity * ball2.mass)
843
844
# Update physics
845
ball1.update(dt)
846
ball2.update(dt)
847
848
# Check collision between balls
849
distance = ball1.position.distance_to(ball2.position)
850
if distance < ball1.radius + ball2.radius:
851
# Simple collision response
852
normal = (ball2.position - ball1.position).normalize()
853
relative_velocity = ball2.velocity - ball1.velocity
854
impulse = 2 * relative_velocity.dot(normal) / (ball1.mass + ball2.mass)
855
856
ball1.velocity += impulse * ball2.mass * normal
857
ball2.velocity -= impulse * ball1.mass * normal
858
```