0
# Mathematical Utilities
1
2
Pymunk provides comprehensive 2D mathematics utilities including vectors, bounding boxes, transformations, and collision filters. These classes form the foundation for all physics calculations and spatial operations.
3
4
## Vec2d - 2D Vector Class
5
6
The `Vec2d` class is an immutable 2D vector with extensive mathematical operations and geometric functions.
7
8
### Class Definition
9
10
```python { .api }
11
class Vec2d(NamedTuple):
12
"""
13
Immutable 2D vector class with extensive mathematical operations.
14
15
Supports vector and scalar operators, geometric calculations,
16
and provides high-level functions for common operations.
17
"""
18
19
x: float
20
y: float
21
22
def __init__(self, x: float = 0, y: float = 0) -> None:
23
"""
24
Create a 2D vector.
25
26
Args:
27
x: X component
28
y: Y component
29
30
Examples:
31
Vec2d(1, 2) # Direct creation
32
Vec2d(*xy) # From tuple unpacking
33
Vec2d() # Zero vector (0, 0)
34
"""
35
```
36
37
### Properties
38
39
```python { .api }
40
# Vector components (inherited from NamedTuple)
41
v.x: float
42
"""X component of the vector"""
43
44
v.y: float
45
"""Y component of the vector"""
46
47
# Magnitude properties
48
v.length: float
49
"""Magnitude/length of the vector: sqrt(x² + y²)"""
50
51
v.length_squared: float
52
"""Squared magnitude (faster than length): x² + y²"""
53
54
# Angular properties
55
v.angle: float
56
"""Angle in radians calculated with atan2(y, x)"""
57
58
v.angle_degrees: float
59
"""Angle in degrees"""
60
```
61
62
### Arithmetic Operations
63
64
```python { .api }
65
# Vector arithmetic (returns new Vec2d)
66
v1 + v2 # Vector addition
67
v1 - v2 # Vector subtraction
68
v1 * scalar # Scalar multiplication
69
scalar * v1 # Scalar multiplication (commutative)
70
v1 / scalar # Scalar division
71
v1 // scalar # Integer division
72
73
# Unary operations
74
+v1 # Positive (returns copy)
75
-v1 # Negation
76
abs(v1) # Magnitude as float
77
78
# Comparison
79
v1 == v2 # Equality
80
v1 != v2 # Inequality
81
82
# Examples:
83
v1 = Vec2d(10, 20)
84
v2 = Vec2d(5, 15)
85
result = v1 + v2 # Vec2d(15, 35)
86
scaled = v1 * 2 # Vec2d(20, 40)
87
length = abs(v1) # 22.36 (approximately)
88
```
89
90
### Vector Operations
91
92
```python { .api }
93
def normalized(self) -> 'Vec2d':
94
"""
95
Return unit vector (length 1) in same direction.
96
97
Returns Vec2d(0, 0) for zero-length vectors.
98
99
Example:
100
v = Vec2d(3, 4)
101
unit = v.normalized() # Vec2d(0.6, 0.8)
102
"""
103
104
def normalized_and_length(self) -> tuple['Vec2d', float]:
105
"""
106
Return normalized vector and original length.
107
108
More efficient than calling normalized() and length separately.
109
110
Returns:
111
(normalized_vector, original_length)
112
113
Example:
114
v = Vec2d(3, 4)
115
unit, length = v.normalized_and_length() # (Vec2d(0.6, 0.8), 5.0)
116
"""
117
118
def scale_to_length(self, length: float) -> 'Vec2d':
119
"""
120
Return vector scaled to specific length.
121
122
Args:
123
length: Desired length
124
125
Raises:
126
ZeroDivisionError: If vector has zero length
127
128
Example:
129
v = Vec2d(3, 4)
130
scaled = v.scale_to_length(10) # Length 10 vector in same direction
131
"""
132
133
def perpendicular(self) -> 'Vec2d':
134
"""
135
Return perpendicular vector (rotated 90° counter-clockwise).
136
137
Example:
138
v = Vec2d(1, 0)
139
perp = v.perpendicular() # Vec2d(0, 1)
140
"""
141
142
def perpendicular_normal(self) -> 'Vec2d':
143
"""Return normalized perpendicular vector."""
144
145
def projection(self, other: tuple[float, float]) -> 'Vec2d':
146
"""
147
Return vector projection onto another vector.
148
149
Args:
150
other: Vector to project onto
151
152
Example:
153
v1 = Vec2d(10, 5)
154
v2 = Vec2d(1, 0)
155
proj = v1.projection(v2) # Vec2d(10, 0) - projection onto x-axis
156
"""
157
```
158
159
### Rotation Operations
160
161
```python { .api }
162
def rotated(self, angle_radians: float) -> 'Vec2d':
163
"""
164
Return vector rotated by angle in radians.
165
166
Args:
167
angle_radians: Rotation angle (positive = counter-clockwise)
168
169
Example:
170
v = Vec2d(1, 0)
171
rotated = v.rotated(math.pi/2) # Vec2d(0, 1) - 90° rotation
172
"""
173
174
def rotated_degrees(self, angle_degrees: float) -> 'Vec2d':
175
"""
176
Return vector rotated by angle in degrees.
177
178
Args:
179
angle_degrees: Rotation angle (positive = counter-clockwise)
180
181
Example:
182
v = Vec2d(2, 0)
183
rotated = v.rotated_degrees(90) # Vec2d(0, 2)
184
"""
185
186
def get_angle_between(self, other: tuple[float, float]) -> float:
187
"""
188
Get angle between this vector and another (radians).
189
190
Returns angle in range [-π, π].
191
192
Example:
193
v1 = Vec2d(1, 0)
194
v2 = Vec2d(0, 1)
195
angle = v1.get_angle_between(v2) # π/2 (90 degrees)
196
"""
197
198
def get_angle_degrees_between(self, other: 'Vec2d') -> float:
199
"""Get angle between vectors in degrees."""
200
```
201
202
### Dot and Cross Products
203
204
```python { .api }
205
def dot(self, other: tuple[float, float]) -> float:
206
"""
207
Calculate dot product with another vector.
208
209
Dot product measures similarity of direction:
210
- Positive: vectors point in similar directions
211
- Zero: vectors are perpendicular
212
- Negative: vectors point in opposite directions
213
214
Example:
215
v1 = Vec2d(1, 0)
216
v2 = Vec2d(0, 1)
217
dot = v1.dot(v2) # 0.0 (perpendicular)
218
"""
219
220
def cross(self, other: tuple[float, float]) -> float:
221
"""
222
Calculate 2D cross product (returns scalar).
223
224
Cross product measures perpendicularity:
225
- Positive: other is counter-clockwise from self
226
- Zero: vectors are parallel/anti-parallel
227
- negative: other is clockwise from self
228
229
Example:
230
v1 = Vec2d(1, 0)
231
v2 = Vec2d(0, 1)
232
cross = v1.cross(v2) # 1.0 (v2 is 90° CCW from v1)
233
"""
234
```
235
236
### Static Factory Methods
237
238
```python { .api }
239
@staticmethod
240
def from_polar(length: float, angle_radians: float) -> 'Vec2d':
241
"""
242
Create vector from polar coordinates.
243
244
Args:
245
length: Vector magnitude
246
angle_radians: Angle from positive x-axis
247
248
Example:
249
v = Vec2d.from_polar(5, math.pi/4) # 45° angle, length 5
250
# Result: Vec2d(3.54, 3.54) approximately
251
"""
252
253
@staticmethod
254
def unit() -> 'Vec2d':
255
"""Return unit vector pointing along positive x-axis: Vec2d(1, 0)"""
256
257
@staticmethod
258
def zero() -> 'Vec2d':
259
"""Return zero vector: Vec2d(0, 0)"""
260
```
261
262
### Indexing and Conversion
263
264
```python { .api }
265
# Indexing support
266
v[0] # Same as v.x
267
v[1] # Same as v.y
268
len(v) # Always 2
269
270
# Conversion
271
list(v) # [x, y]
272
tuple(v) # (x, y)
273
iter(v) # Iterator over (x, y)
274
275
# String representation
276
str(v) # "Vec2d(x, y)"
277
repr(v) # "Vec2d(x, y)"
278
```
279
280
## BB - Bounding Box Class
281
282
Axis-aligned bounding box for efficient spatial operations and collision detection.
283
284
### Class Definition
285
286
```python { .api }
287
class BB(NamedTuple):
288
"""
289
Axis-aligned bounding box stored as left, bottom, right, top values.
290
291
Uses mathematical coordinate system (bottom < top).
292
"""
293
294
left: float = 0
295
bottom: float = 0
296
right: float = 0
297
top: float = 0
298
299
# Examples:
300
BB(left=10, bottom=20, right=100, top=80)
301
BB(right=50, top=30) # Partial specification (left=0, bottom=0)
302
```
303
304
### Factory Methods
305
306
```python { .api }
307
@staticmethod
308
def newForCircle(center: tuple[float, float], radius: float) -> 'BB':
309
"""
310
Create bounding box for a circle.
311
312
Args:
313
center: Circle center point
314
radius: Circle radius
315
316
Example:
317
bb = BB.newForCircle((100, 200), 25)
318
# Result: BB(left=75, bottom=175, right=125, top=225)
319
"""
320
```
321
322
### Query Operations
323
324
```python { .api }
325
def intersects(self, other: 'BB') -> bool:
326
"""
327
Test if two bounding boxes intersect.
328
329
Example:
330
bb1 = BB(0, 0, 50, 50)
331
bb2 = BB(25, 25, 75, 75)
332
overlaps = bb1.intersects(bb2) # True
333
"""
334
335
def intersects_segment(
336
self,
337
a: tuple[float, float],
338
b: tuple[float, float]
339
) -> bool:
340
"""
341
Test if line segment intersects bounding box.
342
343
Args:
344
a: Segment start point
345
b: Segment end point
346
"""
347
348
def contains(self, other: 'BB') -> bool:
349
"""Test if this bounding box completely contains another."""
350
351
def contains_vect(self, point: tuple[float, float]) -> bool:
352
"""
353
Test if point is inside bounding box.
354
355
Example:
356
bb = BB(0, 0, 100, 100)
357
inside = bb.contains_vect((50, 50)) # True
358
outside = bb.contains_vect((150, 50)) # False
359
"""
360
```
361
362
### Geometric Operations
363
364
```python { .api }
365
def merge(self, other: 'BB') -> 'BB':
366
"""
367
Return bounding box that contains both this and other.
368
369
Example:
370
bb1 = BB(0, 0, 50, 50)
371
bb2 = BB(25, 25, 100, 100)
372
merged = bb1.merge(bb2) # BB(0, 0, 100, 100)
373
"""
374
375
def expand(self, v: tuple[float, float]) -> 'BB':
376
"""
377
Return bounding box expanded to contain point v.
378
379
Example:
380
bb = BB(0, 0, 50, 50)
381
expanded = bb.expand((75, 25)) # BB(0, 0, 75, 50)
382
"""
383
384
def center(self) -> Vec2d:
385
"""
386
Return center point of bounding box.
387
388
Example:
389
bb = BB(0, 0, 100, 100)
390
center = bb.center() # Vec2d(50, 50)
391
"""
392
393
def area(self) -> float:
394
"""
395
Calculate area of bounding box.
396
397
Example:
398
bb = BB(0, 0, 50, 40)
399
area = bb.area() # 2000.0
400
"""
401
402
def merged_area(self, other: 'BB') -> float:
403
"""Calculate area of bounding box that would contain both."""
404
```
405
406
### Advanced Operations
407
408
```python { .api }
409
def segment_query(
410
self,
411
a: tuple[float, float],
412
b: tuple[float, float]
413
) -> float:
414
"""
415
Return fraction along segment where bounding box is hit.
416
417
Returns infinity if segment doesn't hit.
418
419
Args:
420
a: Segment start
421
b: Segment end
422
423
Returns:
424
Fraction t where hit_point = a + t * (b - a)
425
"""
426
427
def clamp_vect(self, v: tuple[float, float]) -> Vec2d:
428
"""
429
Clamp point to bounding box (find closest point inside).
430
431
Example:
432
bb = BB(0, 0, 100, 100)
433
clamped = bb.clamp_vect((150, 50)) # Vec2d(100, 50)
434
"""
435
436
def wrap_vect(self, v: tuple[float, float]) -> Vec2d:
437
"""
438
Wrap point around bounding box (modulo operation).
439
440
Useful for implementing wrapping/toroidal spaces.
441
"""
442
```
443
444
## Transform - 2D Transformation Matrix
445
446
Represents 2D affine transformations for scaling, rotation, translation, and skewing operations.
447
448
### Class Definition
449
450
```python { .api }
451
class Transform(NamedTuple):
452
"""
453
2x3 affine transformation matrix for 2D operations.
454
455
Matrix layout:
456
| a c tx | | x |
457
| b d ty | @ | y |
458
| 0 0 1 | | 1 |
459
460
Supports composition via matrix multiplication operator (@).
461
"""
462
463
a: float = 1 # X-axis scaling/rotation
464
b: float = 0 # X-axis skew
465
c: float = 0 # Y-axis skew
466
d: float = 1 # Y-axis scaling/rotation
467
tx: float = 0 # X translation
468
ty: float = 0 # Y translation
469
470
# Examples:
471
Transform(a=2, d=2) # 2x scaling
472
Transform(tx=100, ty=50) # Translation
473
Transform(1, 2, 3, 4, 5, 6) # Full specification
474
```
475
476
### Static Factory Methods
477
478
```python { .api }
479
@staticmethod
480
def identity() -> 'Transform':
481
"""
482
Return identity transform (no transformation).
483
484
Example:
485
t = Transform.identity() # Transform(1, 0, 0, 1, 0, 0)
486
"""
487
488
@staticmethod
489
def translation(tx: float, ty: float) -> 'Transform':
490
"""
491
Create translation transform.
492
493
Args:
494
tx: X translation
495
ty: Y translation
496
497
Example:
498
t = Transform.translation(100, 50)
499
point = t @ Vec2d(0, 0) # Vec2d(100, 50)
500
"""
501
502
@staticmethod
503
def scaling(s: float) -> 'Transform':
504
"""
505
Create uniform scaling transform.
506
507
Args:
508
s: Scale factor (2.0 = double size, 0.5 = half size)
509
510
Example:
511
t = Transform.scaling(2)
512
point = t @ Vec2d(10, 20) # Vec2d(20, 40)
513
"""
514
515
@staticmethod
516
def rotation(angle_radians: float) -> 'Transform':
517
"""
518
Create rotation transform.
519
520
Args:
521
angle_radians: Rotation angle (positive = counter-clockwise)
522
523
Example:
524
t = Transform.rotation(math.pi/2) # 90° rotation
525
point = t @ Vec2d(1, 0) # Vec2d(0, 1)
526
"""
527
```
528
529
### Transform Operations
530
531
```python { .api }
532
def __matmul__(self, other: Union[Vec2d, tuple, 'Transform']) -> Union[Vec2d, 'Transform']:
533
"""
534
Apply transform using @ operator.
535
536
Can transform points/vectors or compose with other transforms.
537
538
Examples:
539
# Transform points
540
t = Transform.scaling(2)
541
result = t @ Vec2d(10, 20) # Vec2d(20, 40)
542
543
# Compose transforms
544
t1 = Transform.scaling(2)
545
t2 = Transform.translation(10, 5)
546
composed = t1 @ t2 # Scale then translate
547
"""
548
549
def translated(self, tx: float, ty: float) -> 'Transform':
550
"""
551
Return transform with additional translation.
552
553
Example:
554
t = Transform.scaling(2)
555
t_moved = t.translated(100, 50) # Scale and translate
556
"""
557
558
def scaled(self, s: float) -> 'Transform':
559
"""
560
Return transform with additional scaling.
561
562
Example:
563
t = Transform.translation(100, 50)
564
t_scaled = t.scaled(2) # Translate and scale
565
"""
566
567
def rotated(self, angle_radians: float) -> 'Transform':
568
"""
569
Return transform with additional rotation.
570
571
Example:
572
t = Transform.scaling(2)
573
t_rotated = t.rotated(math.pi/4) # Scale and rotate 45°
574
"""
575
```
576
577
## ShapeFilter - Collision Filtering
578
579
Controls which shapes can collide with each other using groups, categories, and masks.
580
581
### Class Definition
582
583
```python { .api }
584
class ShapeFilter(NamedTuple):
585
"""
586
Collision filter using groups, categories, and category masks.
587
588
Provides efficient collision filtering before expensive collision detection.
589
"""
590
591
group: int = 0
592
"""
593
Collision group - shapes with same non-zero group don't collide.
594
Use for complex objects where parts shouldn't collide with each other.
595
"""
596
597
categories: int = 0xFFFFFFFF # All categories by default
598
"""
599
Categories this shape belongs to (bitmask).
600
Shape can belong to multiple categories.
601
"""
602
603
mask: int = 0xFFFFFFFF # Collide with all categories by default
604
"""
605
Categories this shape collides with (bitmask).
606
Must have overlapping bits with other shape's categories to collide.
607
"""
608
```
609
610
### Static Methods
611
612
```python { .api }
613
@staticmethod
614
def ALL_CATEGORIES() -> int:
615
"""Return bitmask with all category bits set (0xFFFFFFFF)"""
616
617
@staticmethod
618
def ALL_MASKS() -> int:
619
"""Return bitmask matching all categories (0xFFFFFFFF)"""
620
```
621
622
### Collision Logic
623
624
```python { .api }
625
def rejects_collision(self, other: 'ShapeFilter') -> bool:
626
"""
627
Test if this filter rejects collision with another filter.
628
629
Collision rejected if:
630
1. Same non-zero group (shapes in same group don't collide)
631
2. No category overlap (self.categories & other.mask == 0)
632
3. No mask overlap (self.mask & other.categories == 0)
633
634
Example:
635
player_filter = ShapeFilter(categories=0b1, mask=0b1110) # Category 1, collides with 2,3,4
636
enemy_filter = ShapeFilter(categories=0b10, mask=0b1101) # Category 2, collides with 1,3,4
637
638
can_collide = not player_filter.rejects_collision(enemy_filter) # True
639
"""
640
```
641
642
## Usage Examples
643
644
### Vector Mathematics
645
646
```python { .api }
647
import pymunk
648
import math
649
650
# Basic vector operations
651
v1 = pymunk.Vec2d(10, 20)
652
v2 = pymunk.Vec2d(5, 15)
653
654
# Arithmetic
655
sum_v = v1 + v2 # Vec2d(15, 35)
656
diff_v = v1 - v2 # Vec2d(5, 5)
657
scaled = v1 * 2.5 # Vec2d(25, 50)
658
magnitude = abs(v1) # 22.36...
659
660
# Normalization and direction
661
unit_v = v1.normalized() # Vec2d(0.447, 0.894)
662
direction = (v2 - v1).normalized() # Direction from v1 to v2
663
664
# Angles and rotation
665
angle = v1.angle # Angle in radians
666
angle_deg = v1.angle_degrees # Angle in degrees
667
rotated = v1.rotated(math.pi/4) # Rotate 45°
668
669
# Dot product for projections
670
proj_length = v1.dot(v2.normalized()) # Length of v1 projected onto v2 direction
671
projection = v2.normalized() * proj_length
672
673
# Cross product for perpendicularity
674
perp_v = v1.perpendicular() # Vec2d(-20, 10) - 90° CCW rotation
675
cross = v1.cross(v2) # Scalar cross product
676
```
677
678
### Polar Coordinates
679
680
```python { .api }
681
import pymunk
682
import math
683
684
# Create vectors from polar coordinates
685
radius = 100
686
angle = math.pi / 3 # 60 degrees
687
688
velocity = pymunk.Vec2d.from_polar(radius, angle)
689
print(f"Velocity: {velocity}") # Vec2d(50.0, 86.6)
690
691
# Convert back to polar
692
length = abs(velocity)
693
angle = velocity.angle
694
695
# Circular motion
696
def circular_motion(time, radius, angular_velocity):
697
angle = angular_velocity * time
698
return pymunk.Vec2d.from_polar(radius, angle)
699
700
# Orbit positions
701
for t in range(10):
702
position = circular_motion(t * 0.1, 50, math.pi)
703
print(f"t={t*0.1:.1f}: {position}")
704
```
705
706
### Bounding Box Operations
707
708
```python { .api }
709
import pymunk
710
711
# Create bounding boxes for shapes
712
circle_bb = pymunk.BB.newForCircle((100, 100), 25)
713
box_bb = pymunk.BB(left=50, bottom=50, right=150, top=150)
714
715
# Test intersections
716
if circle_bb.intersects(box_bb):
717
print("Circle and box overlap")
718
719
# Spatial partitioning
720
def get_grid_cell(position, cell_size):
721
"""Get grid cell coordinates for spatial partitioning"""
722
x = int(position.x // cell_size) * cell_size
723
y = int(position.y // cell_size) * cell_size
724
return pymunk.BB(x, y, x + cell_size, y + cell_size)
725
726
# Expand bounding box to contain objects
727
world_bb = pymunk.BB(0, 0, 0, 0)
728
objects = [pymunk.Vec2d(100, 200), pymunk.Vec2d(300, 150), pymunk.Vec2d(50, 400)]
729
730
for obj_pos in objects:
731
world_bb = world_bb.expand(obj_pos)
732
733
print(f"World bounds: {world_bb}")
734
```
735
736
### Transform Compositions
737
738
```python { .api }
739
import pymunk
740
import math
741
742
# Object transformation pipeline
743
def transform_object(position, scale, rotation, translation):
744
"""Apply scale, rotation, then translation"""
745
746
# Create individual transforms
747
scale_t = pymunk.Transform.scaling(scale)
748
rotate_t = pymunk.Transform.rotation(rotation)
749
translate_t = pymunk.Transform.translation(*translation)
750
751
# Compose transforms (order matters!)
752
combined = translate_t @ rotate_t @ scale_t
753
754
# Apply to position
755
return combined @ position
756
757
# Example: Scale by 2, rotate 45°, move to (100, 100)
758
original = pymunk.Vec2d(10, 0)
759
transformed = transform_object(
760
original,
761
scale=2,
762
rotation=math.pi/4,
763
translation=(100, 100)
764
)
765
766
# Camera/viewport transform
767
def screen_to_world(screen_pos, camera_pos, camera_zoom):
768
"""Convert screen coordinates to world coordinates"""
769
770
# Screen transform: translate to center, scale by zoom, translate by camera
771
screen_center = pymunk.Transform.translation(-400, -300) # Assume 800x600 screen
772
zoom_t = pymunk.Transform.scaling(1.0 / camera_zoom)
773
camera_t = pymunk.Transform.translation(*camera_pos)
774
775
world_transform = camera_t @ zoom_t @ screen_center
776
return world_transform @ screen_pos
777
```
778
779
### Collision Filtering System
780
781
```python { .api }
782
import pymunk
783
784
# Define collision categories (using powers of 2 for bitmasks)
785
PLAYER = 0b00001 # 1
786
ENEMY = 0b00010 # 2
787
PLAYER_BULLET = 0b00100 # 4
788
ENEMY_BULLET = 0b01000 # 8
789
WALLS = 0b10000 # 16
790
791
# Create collision filters
792
player_filter = pymunk.ShapeFilter(
793
categories=PLAYER,
794
mask=ENEMY | ENEMY_BULLET | WALLS # Collide with enemies, enemy bullets, walls
795
)
796
797
enemy_filter = pymunk.ShapeFilter(
798
categories=ENEMY,
799
mask=PLAYER | PLAYER_BULLET | WALLS # Collide with player, player bullets, walls
800
)
801
802
player_bullet_filter = pymunk.ShapeFilter(
803
categories=PLAYER_BULLET,
804
mask=ENEMY | WALLS # Only hit enemies and walls
805
)
806
807
enemy_bullet_filter = pymunk.ShapeFilter(
808
categories=ENEMY_BULLET,
809
mask=PLAYER | WALLS # Only hit player and walls
810
)
811
812
wall_filter = pymunk.ShapeFilter(
813
categories=WALLS,
814
mask=PLAYER | ENEMY | PLAYER_BULLET | ENEMY_BULLET # Hit everything
815
)
816
817
# Ragdoll example - body parts don't collide with each other
818
RAGDOLL_GROUP = 1
819
820
head_filter = pymunk.ShapeFilter(group=RAGDOLL_GROUP, categories=PLAYER, mask=WALLS)
821
torso_filter = pymunk.ShapeFilter(group=RAGDOLL_GROUP, categories=PLAYER, mask=WALLS)
822
arm_filter = pymunk.ShapeFilter(group=RAGDOLL_GROUP, categories=PLAYER, mask=WALLS)
823
824
# All ragdoll parts have same group, so they won't collide with each other
825
826
# Team-based filtering
827
RED_TEAM = 0b100000 # 32
828
BLUE_TEAM = 0b1000000 # 64
829
830
red_player_filter = pymunk.ShapeFilter(
831
categories=RED_TEAM | PLAYER,
832
mask=BLUE_TEAM | WALLS # Only collide with blue team and walls
833
)
834
835
blue_player_filter = pymunk.ShapeFilter(
836
categories=BLUE_TEAM | PLAYER,
837
mask=RED_TEAM | WALLS # Only collide with red team and walls
838
)
839
```
840
841
## Area Calculation Functions
842
843
Pymunk provides utilities for calculating areas of geometric shapes, useful for physics calculations and shape analysis.
844
845
### Area Functions
846
847
```python { .api }
848
def area_for_circle(inner_radius: float, outer_radius: float) -> float:
849
"""
850
Calculate area of a hollow circle.
851
852
For solid circles, use inner_radius=0.
853
854
Args:
855
inner_radius: Inner radius (0 for solid circle)
856
outer_radius: Outer radius
857
858
Returns:
859
Area of the hollow circle shape
860
861
Example:
862
solid_area = area_for_circle(0, 25) # Solid circle, radius 25
863
ring_area = area_for_circle(20, 25) # Ring from radius 20 to 25
864
"""
865
866
def area_for_segment(
867
a: tuple[float, float],
868
b: tuple[float, float],
869
radius: float
870
) -> float:
871
"""
872
Calculate area of a beveled segment.
873
874
Will always be zero if radius is zero (line has no area).
875
876
Args:
877
a: Start point of the segment
878
b: End point of the segment
879
radius: Radius/thickness of the segment
880
881
Returns:
882
Area of the beveled segment
883
884
Example:
885
# Thick line segment from (0,0) to (100,0) with radius 5
886
area = area_for_segment((0, 0), (100, 0), 5)
887
"""
888
889
def area_for_poly(
890
vertices: Sequence[tuple[float, float]],
891
radius: float = 0
892
) -> float:
893
"""
894
Calculate signed area of a polygon shape.
895
896
Returns negative number for polygons with clockwise winding.
897
898
Args:
899
vertices: List of vertex coordinates
900
radius: Optional radius for rounded corners
901
902
Returns:
903
Signed area (negative for clockwise polygons)
904
905
Example:
906
# Rectangle vertices (counter-clockwise)
907
vertices = [(0, 0), (50, 0), (50, 30), (0, 30)]
908
area = area_for_poly(vertices) # Positive area: 1500.0
909
910
# Same vertices clockwise
911
vertices_cw = [(0, 0), (0, 30), (50, 30), (50, 0)]
912
area_cw = area_for_poly(vertices_cw) # Negative area: -1500.0
913
"""
914
```
915
916
## Moment of Inertia Calculation Functions
917
918
Pymunk provides functions to calculate moments of inertia for different shape types, essential for realistic physics simulations.
919
920
### Moment Functions
921
922
```python { .api }
923
def moment_for_circle(
924
mass: float,
925
inner_radius: float,
926
outer_radius: float,
927
offset: tuple[float, float] = (0, 0)
928
) -> float:
929
"""
930
Calculate moment of inertia for a hollow circle.
931
932
A solid circle has an inner radius of 0.
933
934
Args:
935
mass: Mass of the shape
936
inner_radius: Inner radius (0 for solid circle)
937
outer_radius: Outer radius
938
offset: Offset from body center (default: (0, 0))
939
940
Returns:
941
Moment of inertia value
942
943
Example:
944
# Solid disk
945
solid_moment = moment_for_circle(mass=10, inner_radius=0, outer_radius=25)
946
947
# Ring/hollow circle
948
ring_moment = moment_for_circle(mass=10, inner_radius=20, outer_radius=25)
949
950
# Off-center circle
951
offset_moment = moment_for_circle(mass=10, inner_radius=0, outer_radius=15, offset=(10, 5))
952
"""
953
954
def moment_for_segment(
955
mass: float,
956
a: tuple[float, float],
957
b: tuple[float, float],
958
radius: float
959
) -> float:
960
"""
961
Calculate moment of inertia for a line segment.
962
963
The endpoints a and b are relative to the body center.
964
965
Args:
966
mass: Mass of the segment
967
a: Start point relative to body
968
b: End point relative to body
969
radius: Thickness radius of the segment
970
971
Returns:
972
Moment of inertia value
973
974
Example:
975
# Horizontal line segment
976
moment = moment_for_segment(mass=5, a=(0, 0), b=(100, 0), radius=2)
977
978
# Vertical line segment
979
moment = moment_for_segment(mass=5, a=(0, -50), b=(0, 50), radius=3)
980
"""
981
982
def moment_for_box(mass: float, size: tuple[float, float]) -> float:
983
"""
984
Calculate moment of inertia for a solid box centered on the body.
985
986
Args:
987
mass: Mass of the box
988
size: (width, height) of the box
989
990
Returns:
991
Moment of inertia value
992
993
Example:
994
# Square box 50x50
995
moment = moment_for_box(mass=10, size=(50, 50))
996
997
# Rectangular box 100x30
998
moment = moment_for_box(mass=15, size=(100, 30))
999
"""
1000
1001
def moment_for_poly(
1002
mass: float,
1003
vertices: Sequence[tuple[float, float]],
1004
offset: tuple[float, float] = (0, 0),
1005
radius: float = 0
1006
) -> float:
1007
"""
1008
Calculate moment of inertia for a solid polygon shape.
1009
1010
Assumes the polygon center of gravity is at its centroid.
1011
The offset is added to each vertex.
1012
1013
Args:
1014
mass: Mass of the polygon
1015
vertices: List of vertex coordinates
1016
offset: Offset added to each vertex (default: (0, 0))
1017
radius: Optional radius for rounded corners
1018
1019
Returns:
1020
Moment of inertia value
1021
1022
Example:
1023
# Triangle
1024
triangle_vertices = [(0, 0), (50, 0), (25, 43)]
1025
moment = moment_for_poly(mass=8, vertices=triangle_vertices)
1026
1027
# Rectangle with offset
1028
rect_vertices = [(0, 0), (40, 0), (40, 20), (0, 20)]
1029
moment = moment_for_poly(mass=12, vertices=rect_vertices, offset=(10, 5))
1030
1031
# Rounded polygon
1032
moment = moment_for_poly(mass=10, vertices=rect_vertices, radius=3)
1033
"""
1034
```
1035
1036
## Utility Callback Functions
1037
1038
### Empty Callback Function
1039
1040
```python { .api }
1041
def empty_callback(*args: Any, **kwargs: Any) -> None:
1042
"""
1043
A default empty callback function.
1044
1045
Can be used to reset a collision callback to its original empty
1046
function. More efficient than defining your own empty/do-nothing method.
1047
1048
Args:
1049
*args: Any positional arguments (ignored)
1050
**kwargs: Any keyword arguments (ignored)
1051
1052
Example:
1053
# Reset collision handler to do nothing
1054
handler = space.add_collision_handler(1, 2)
1055
handler.begin = empty_callback
1056
handler.pre_solve = empty_callback
1057
1058
# Or use directly as a placeholder
1059
space.add_collision_handler(3, 4).begin = empty_callback
1060
"""
1061
```
1062
1063
### Moment of Inertia Calculations
1064
1065
```python { .api }
1066
import pymunk
1067
1068
# Calculate moments of inertia for different shapes
1069
mass = 10
1070
1071
# Circle (solid disk)
1072
circle_moment = pymunk.moment_for_circle(mass, 0, 25) # inner_radius=0, outer_radius=25
1073
1074
# Hollow circle (ring)
1075
ring_moment = pymunk.moment_for_circle(mass, 20, 25) # inner_radius=20, outer_radius=25
1076
1077
# Box
1078
box_moment = pymunk.moment_for_box(mass, (50, 30)) # width=50, height=30
1079
1080
# Polygon
1081
vertices = [(0, 0), (50, 0), (50, 30), (0, 30)]
1082
poly_moment = pymunk.moment_for_poly(mass, vertices)
1083
1084
# Line segment
1085
segment_moment = pymunk.moment_for_segment(mass, (0, 0), (100, 0), radius=5)
1086
1087
# Compound shape (multiple shapes on same body)
1088
def create_compound_body():
1089
# Let pymunk calculate total mass/moment from shapes
1090
body = pymunk.Body()
1091
1092
# Add multiple shapes with different densities
1093
circle = pymunk.Circle(body, 25, (0, 0))
1094
circle.density = 1.0
1095
1096
box = pymunk.Poly.create_box(body, (40, 20), radius=2)
1097
box.density = 2.0 # Denser material
1098
1099
return body, [circle, box]
1100
```
1101
1102
These mathematical utilities provide the foundation for all physics calculations, spatial queries, and geometric operations in Pymunk, offering both performance and ease of use for 2D physics applications.