Pymunk is a easy-to-use pythonic 2D physics library built on Munk2D
—
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.
The Vec2d class is an immutable 2D vector with extensive mathematical operations and geometric functions.
class Vec2d(NamedTuple):
"""
Immutable 2D vector class with extensive mathematical operations.
Supports vector and scalar operators, geometric calculations,
and provides high-level functions for common operations.
"""
x: float
y: float
def __init__(self, x: float = 0, y: float = 0) -> None:
"""
Create a 2D vector.
Args:
x: X component
y: Y component
Examples:
Vec2d(1, 2) # Direct creation
Vec2d(*xy) # From tuple unpacking
Vec2d() # Zero vector (0, 0)
"""# Vector components (inherited from NamedTuple)
v.x: float
"""X component of the vector"""
v.y: float
"""Y component of the vector"""
# Magnitude properties
v.length: float
"""Magnitude/length of the vector: sqrt(x² + y²)"""
v.length_squared: float
"""Squared magnitude (faster than length): x² + y²"""
# Angular properties
v.angle: float
"""Angle in radians calculated with atan2(y, x)"""
v.angle_degrees: float
"""Angle in degrees"""# Vector arithmetic (returns new Vec2d)
v1 + v2 # Vector addition
v1 - v2 # Vector subtraction
v1 * scalar # Scalar multiplication
scalar * v1 # Scalar multiplication (commutative)
v1 / scalar # Scalar division
v1 // scalar # Integer division
# Unary operations
+v1 # Positive (returns copy)
-v1 # Negation
abs(v1) # Magnitude as float
# Comparison
v1 == v2 # Equality
v1 != v2 # Inequality
# Examples:
v1 = Vec2d(10, 20)
v2 = Vec2d(5, 15)
result = v1 + v2 # Vec2d(15, 35)
scaled = v1 * 2 # Vec2d(20, 40)
length = abs(v1) # 22.36 (approximately)def normalized(self) -> 'Vec2d':
"""
Return unit vector (length 1) in same direction.
Returns Vec2d(0, 0) for zero-length vectors.
Example:
v = Vec2d(3, 4)
unit = v.normalized() # Vec2d(0.6, 0.8)
"""
def normalized_and_length(self) -> tuple['Vec2d', float]:
"""
Return normalized vector and original length.
More efficient than calling normalized() and length separately.
Returns:
(normalized_vector, original_length)
Example:
v = Vec2d(3, 4)
unit, length = v.normalized_and_length() # (Vec2d(0.6, 0.8), 5.0)
"""
def scale_to_length(self, length: float) -> 'Vec2d':
"""
Return vector scaled to specific length.
Args:
length: Desired length
Raises:
ZeroDivisionError: If vector has zero length
Example:
v = Vec2d(3, 4)
scaled = v.scale_to_length(10) # Length 10 vector in same direction
"""
def perpendicular(self) -> 'Vec2d':
"""
Return perpendicular vector (rotated 90° counter-clockwise).
Example:
v = Vec2d(1, 0)
perp = v.perpendicular() # Vec2d(0, 1)
"""
def perpendicular_normal(self) -> 'Vec2d':
"""Return normalized perpendicular vector."""
def projection(self, other: tuple[float, float]) -> 'Vec2d':
"""
Return vector projection onto another vector.
Args:
other: Vector to project onto
Example:
v1 = Vec2d(10, 5)
v2 = Vec2d(1, 0)
proj = v1.projection(v2) # Vec2d(10, 0) - projection onto x-axis
"""def rotated(self, angle_radians: float) -> 'Vec2d':
"""
Return vector rotated by angle in radians.
Args:
angle_radians: Rotation angle (positive = counter-clockwise)
Example:
v = Vec2d(1, 0)
rotated = v.rotated(math.pi/2) # Vec2d(0, 1) - 90° rotation
"""
def rotated_degrees(self, angle_degrees: float) -> 'Vec2d':
"""
Return vector rotated by angle in degrees.
Args:
angle_degrees: Rotation angle (positive = counter-clockwise)
Example:
v = Vec2d(2, 0)
rotated = v.rotated_degrees(90) # Vec2d(0, 2)
"""
def get_angle_between(self, other: tuple[float, float]) -> float:
"""
Get angle between this vector and another (radians).
Returns angle in range [-π, π].
Example:
v1 = Vec2d(1, 0)
v2 = Vec2d(0, 1)
angle = v1.get_angle_between(v2) # π/2 (90 degrees)
"""
def get_angle_degrees_between(self, other: 'Vec2d') -> float:
"""Get angle between vectors in degrees."""def dot(self, other: tuple[float, float]) -> float:
"""
Calculate dot product with another vector.
Dot product measures similarity of direction:
- Positive: vectors point in similar directions
- Zero: vectors are perpendicular
- Negative: vectors point in opposite directions
Example:
v1 = Vec2d(1, 0)
v2 = Vec2d(0, 1)
dot = v1.dot(v2) # 0.0 (perpendicular)
"""
def cross(self, other: tuple[float, float]) -> float:
"""
Calculate 2D cross product (returns scalar).
Cross product measures perpendicularity:
- Positive: other is counter-clockwise from self
- Zero: vectors are parallel/anti-parallel
- negative: other is clockwise from self
Example:
v1 = Vec2d(1, 0)
v2 = Vec2d(0, 1)
cross = v1.cross(v2) # 1.0 (v2 is 90° CCW from v1)
"""@staticmethod
def from_polar(length: float, angle_radians: float) -> 'Vec2d':
"""
Create vector from polar coordinates.
Args:
length: Vector magnitude
angle_radians: Angle from positive x-axis
Example:
v = Vec2d.from_polar(5, math.pi/4) # 45° angle, length 5
# Result: Vec2d(3.54, 3.54) approximately
"""
@staticmethod
def unit() -> 'Vec2d':
"""Return unit vector pointing along positive x-axis: Vec2d(1, 0)"""
@staticmethod
def zero() -> 'Vec2d':
"""Return zero vector: Vec2d(0, 0)"""# Indexing support
v[0] # Same as v.x
v[1] # Same as v.y
len(v) # Always 2
# Conversion
list(v) # [x, y]
tuple(v) # (x, y)
iter(v) # Iterator over (x, y)
# String representation
str(v) # "Vec2d(x, y)"
repr(v) # "Vec2d(x, y)"Axis-aligned bounding box for efficient spatial operations and collision detection.
class BB(NamedTuple):
"""
Axis-aligned bounding box stored as left, bottom, right, top values.
Uses mathematical coordinate system (bottom < top).
"""
left: float = 0
bottom: float = 0
right: float = 0
top: float = 0
# Examples:
BB(left=10, bottom=20, right=100, top=80)
BB(right=50, top=30) # Partial specification (left=0, bottom=0)@staticmethod
def newForCircle(center: tuple[float, float], radius: float) -> 'BB':
"""
Create bounding box for a circle.
Args:
center: Circle center point
radius: Circle radius
Example:
bb = BB.newForCircle((100, 200), 25)
# Result: BB(left=75, bottom=175, right=125, top=225)
"""def intersects(self, other: 'BB') -> bool:
"""
Test if two bounding boxes intersect.
Example:
bb1 = BB(0, 0, 50, 50)
bb2 = BB(25, 25, 75, 75)
overlaps = bb1.intersects(bb2) # True
"""
def intersects_segment(
self,
a: tuple[float, float],
b: tuple[float, float]
) -> bool:
"""
Test if line segment intersects bounding box.
Args:
a: Segment start point
b: Segment end point
"""
def contains(self, other: 'BB') -> bool:
"""Test if this bounding box completely contains another."""
def contains_vect(self, point: tuple[float, float]) -> bool:
"""
Test if point is inside bounding box.
Example:
bb = BB(0, 0, 100, 100)
inside = bb.contains_vect((50, 50)) # True
outside = bb.contains_vect((150, 50)) # False
"""def merge(self, other: 'BB') -> 'BB':
"""
Return bounding box that contains both this and other.
Example:
bb1 = BB(0, 0, 50, 50)
bb2 = BB(25, 25, 100, 100)
merged = bb1.merge(bb2) # BB(0, 0, 100, 100)
"""
def expand(self, v: tuple[float, float]) -> 'BB':
"""
Return bounding box expanded to contain point v.
Example:
bb = BB(0, 0, 50, 50)
expanded = bb.expand((75, 25)) # BB(0, 0, 75, 50)
"""
def center(self) -> Vec2d:
"""
Return center point of bounding box.
Example:
bb = BB(0, 0, 100, 100)
center = bb.center() # Vec2d(50, 50)
"""
def area(self) -> float:
"""
Calculate area of bounding box.
Example:
bb = BB(0, 0, 50, 40)
area = bb.area() # 2000.0
"""
def merged_area(self, other: 'BB') -> float:
"""Calculate area of bounding box that would contain both."""def segment_query(
self,
a: tuple[float, float],
b: tuple[float, float]
) -> float:
"""
Return fraction along segment where bounding box is hit.
Returns infinity if segment doesn't hit.
Args:
a: Segment start
b: Segment end
Returns:
Fraction t where hit_point = a + t * (b - a)
"""
def clamp_vect(self, v: tuple[float, float]) -> Vec2d:
"""
Clamp point to bounding box (find closest point inside).
Example:
bb = BB(0, 0, 100, 100)
clamped = bb.clamp_vect((150, 50)) # Vec2d(100, 50)
"""
def wrap_vect(self, v: tuple[float, float]) -> Vec2d:
"""
Wrap point around bounding box (modulo operation).
Useful for implementing wrapping/toroidal spaces.
"""Represents 2D affine transformations for scaling, rotation, translation, and skewing operations.
class Transform(NamedTuple):
"""
2x3 affine transformation matrix for 2D operations.
Matrix layout:
| a c tx | | x |
| b d ty | @ | y |
| 0 0 1 | | 1 |
Supports composition via matrix multiplication operator (@).
"""
a: float = 1 # X-axis scaling/rotation
b: float = 0 # X-axis skew
c: float = 0 # Y-axis skew
d: float = 1 # Y-axis scaling/rotation
tx: float = 0 # X translation
ty: float = 0 # Y translation
# Examples:
Transform(a=2, d=2) # 2x scaling
Transform(tx=100, ty=50) # Translation
Transform(1, 2, 3, 4, 5, 6) # Full specification@staticmethod
def identity() -> 'Transform':
"""
Return identity transform (no transformation).
Example:
t = Transform.identity() # Transform(1, 0, 0, 1, 0, 0)
"""
@staticmethod
def translation(tx: float, ty: float) -> 'Transform':
"""
Create translation transform.
Args:
tx: X translation
ty: Y translation
Example:
t = Transform.translation(100, 50)
point = t @ Vec2d(0, 0) # Vec2d(100, 50)
"""
@staticmethod
def scaling(s: float) -> 'Transform':
"""
Create uniform scaling transform.
Args:
s: Scale factor (2.0 = double size, 0.5 = half size)
Example:
t = Transform.scaling(2)
point = t @ Vec2d(10, 20) # Vec2d(20, 40)
"""
@staticmethod
def rotation(angle_radians: float) -> 'Transform':
"""
Create rotation transform.
Args:
angle_radians: Rotation angle (positive = counter-clockwise)
Example:
t = Transform.rotation(math.pi/2) # 90° rotation
point = t @ Vec2d(1, 0) # Vec2d(0, 1)
"""def __matmul__(self, other: Union[Vec2d, tuple, 'Transform']) -> Union[Vec2d, 'Transform']:
"""
Apply transform using @ operator.
Can transform points/vectors or compose with other transforms.
Examples:
# Transform points
t = Transform.scaling(2)
result = t @ Vec2d(10, 20) # Vec2d(20, 40)
# Compose transforms
t1 = Transform.scaling(2)
t2 = Transform.translation(10, 5)
composed = t1 @ t2 # Scale then translate
"""
def translated(self, tx: float, ty: float) -> 'Transform':
"""
Return transform with additional translation.
Example:
t = Transform.scaling(2)
t_moved = t.translated(100, 50) # Scale and translate
"""
def scaled(self, s: float) -> 'Transform':
"""
Return transform with additional scaling.
Example:
t = Transform.translation(100, 50)
t_scaled = t.scaled(2) # Translate and scale
"""
def rotated(self, angle_radians: float) -> 'Transform':
"""
Return transform with additional rotation.
Example:
t = Transform.scaling(2)
t_rotated = t.rotated(math.pi/4) # Scale and rotate 45°
"""Controls which shapes can collide with each other using groups, categories, and masks.
class ShapeFilter(NamedTuple):
"""
Collision filter using groups, categories, and category masks.
Provides efficient collision filtering before expensive collision detection.
"""
group: int = 0
"""
Collision group - shapes with same non-zero group don't collide.
Use for complex objects where parts shouldn't collide with each other.
"""
categories: int = 0xFFFFFFFF # All categories by default
"""
Categories this shape belongs to (bitmask).
Shape can belong to multiple categories.
"""
mask: int = 0xFFFFFFFF # Collide with all categories by default
"""
Categories this shape collides with (bitmask).
Must have overlapping bits with other shape's categories to collide.
"""@staticmethod
def ALL_CATEGORIES() -> int:
"""Return bitmask with all category bits set (0xFFFFFFFF)"""
@staticmethod
def ALL_MASKS() -> int:
"""Return bitmask matching all categories (0xFFFFFFFF)"""def rejects_collision(self, other: 'ShapeFilter') -> bool:
"""
Test if this filter rejects collision with another filter.
Collision rejected if:
1. Same non-zero group (shapes in same group don't collide)
2. No category overlap (self.categories & other.mask == 0)
3. No mask overlap (self.mask & other.categories == 0)
Example:
player_filter = ShapeFilter(categories=0b1, mask=0b1110) # Category 1, collides with 2,3,4
enemy_filter = ShapeFilter(categories=0b10, mask=0b1101) # Category 2, collides with 1,3,4
can_collide = not player_filter.rejects_collision(enemy_filter) # True
"""import pymunk
import math
# Basic vector operations
v1 = pymunk.Vec2d(10, 20)
v2 = pymunk.Vec2d(5, 15)
# Arithmetic
sum_v = v1 + v2 # Vec2d(15, 35)
diff_v = v1 - v2 # Vec2d(5, 5)
scaled = v1 * 2.5 # Vec2d(25, 50)
magnitude = abs(v1) # 22.36...
# Normalization and direction
unit_v = v1.normalized() # Vec2d(0.447, 0.894)
direction = (v2 - v1).normalized() # Direction from v1 to v2
# Angles and rotation
angle = v1.angle # Angle in radians
angle_deg = v1.angle_degrees # Angle in degrees
rotated = v1.rotated(math.pi/4) # Rotate 45°
# Dot product for projections
proj_length = v1.dot(v2.normalized()) # Length of v1 projected onto v2 direction
projection = v2.normalized() * proj_length
# Cross product for perpendicularity
perp_v = v1.perpendicular() # Vec2d(-20, 10) - 90° CCW rotation
cross = v1.cross(v2) # Scalar cross productimport pymunk
import math
# Create vectors from polar coordinates
radius = 100
angle = math.pi / 3 # 60 degrees
velocity = pymunk.Vec2d.from_polar(radius, angle)
print(f"Velocity: {velocity}") # Vec2d(50.0, 86.6)
# Convert back to polar
length = abs(velocity)
angle = velocity.angle
# Circular motion
def circular_motion(time, radius, angular_velocity):
angle = angular_velocity * time
return pymunk.Vec2d.from_polar(radius, angle)
# Orbit positions
for t in range(10):
position = circular_motion(t * 0.1, 50, math.pi)
print(f"t={t*0.1:.1f}: {position}")import pymunk
# Create bounding boxes for shapes
circle_bb = pymunk.BB.newForCircle((100, 100), 25)
box_bb = pymunk.BB(left=50, bottom=50, right=150, top=150)
# Test intersections
if circle_bb.intersects(box_bb):
print("Circle and box overlap")
# Spatial partitioning
def get_grid_cell(position, cell_size):
"""Get grid cell coordinates for spatial partitioning"""
x = int(position.x // cell_size) * cell_size
y = int(position.y // cell_size) * cell_size
return pymunk.BB(x, y, x + cell_size, y + cell_size)
# Expand bounding box to contain objects
world_bb = pymunk.BB(0, 0, 0, 0)
objects = [pymunk.Vec2d(100, 200), pymunk.Vec2d(300, 150), pymunk.Vec2d(50, 400)]
for obj_pos in objects:
world_bb = world_bb.expand(obj_pos)
print(f"World bounds: {world_bb}")import pymunk
import math
# Object transformation pipeline
def transform_object(position, scale, rotation, translation):
"""Apply scale, rotation, then translation"""
# Create individual transforms
scale_t = pymunk.Transform.scaling(scale)
rotate_t = pymunk.Transform.rotation(rotation)
translate_t = pymunk.Transform.translation(*translation)
# Compose transforms (order matters!)
combined = translate_t @ rotate_t @ scale_t
# Apply to position
return combined @ position
# Example: Scale by 2, rotate 45°, move to (100, 100)
original = pymunk.Vec2d(10, 0)
transformed = transform_object(
original,
scale=2,
rotation=math.pi/4,
translation=(100, 100)
)
# Camera/viewport transform
def screen_to_world(screen_pos, camera_pos, camera_zoom):
"""Convert screen coordinates to world coordinates"""
# Screen transform: translate to center, scale by zoom, translate by camera
screen_center = pymunk.Transform.translation(-400, -300) # Assume 800x600 screen
zoom_t = pymunk.Transform.scaling(1.0 / camera_zoom)
camera_t = pymunk.Transform.translation(*camera_pos)
world_transform = camera_t @ zoom_t @ screen_center
return world_transform @ screen_posimport pymunk
# Define collision categories (using powers of 2 for bitmasks)
PLAYER = 0b00001 # 1
ENEMY = 0b00010 # 2
PLAYER_BULLET = 0b00100 # 4
ENEMY_BULLET = 0b01000 # 8
WALLS = 0b10000 # 16
# Create collision filters
player_filter = pymunk.ShapeFilter(
categories=PLAYER,
mask=ENEMY | ENEMY_BULLET | WALLS # Collide with enemies, enemy bullets, walls
)
enemy_filter = pymunk.ShapeFilter(
categories=ENEMY,
mask=PLAYER | PLAYER_BULLET | WALLS # Collide with player, player bullets, walls
)
player_bullet_filter = pymunk.ShapeFilter(
categories=PLAYER_BULLET,
mask=ENEMY | WALLS # Only hit enemies and walls
)
enemy_bullet_filter = pymunk.ShapeFilter(
categories=ENEMY_BULLET,
mask=PLAYER | WALLS # Only hit player and walls
)
wall_filter = pymunk.ShapeFilter(
categories=WALLS,
mask=PLAYER | ENEMY | PLAYER_BULLET | ENEMY_BULLET # Hit everything
)
# Ragdoll example - body parts don't collide with each other
RAGDOLL_GROUP = 1
head_filter = pymunk.ShapeFilter(group=RAGDOLL_GROUP, categories=PLAYER, mask=WALLS)
torso_filter = pymunk.ShapeFilter(group=RAGDOLL_GROUP, categories=PLAYER, mask=WALLS)
arm_filter = pymunk.ShapeFilter(group=RAGDOLL_GROUP, categories=PLAYER, mask=WALLS)
# All ragdoll parts have same group, so they won't collide with each other
# Team-based filtering
RED_TEAM = 0b100000 # 32
BLUE_TEAM = 0b1000000 # 64
red_player_filter = pymunk.ShapeFilter(
categories=RED_TEAM | PLAYER,
mask=BLUE_TEAM | WALLS # Only collide with blue team and walls
)
blue_player_filter = pymunk.ShapeFilter(
categories=BLUE_TEAM | PLAYER,
mask=RED_TEAM | WALLS # Only collide with red team and walls
)Pymunk provides utilities for calculating areas of geometric shapes, useful for physics calculations and shape analysis.
def area_for_circle(inner_radius: float, outer_radius: float) -> float:
"""
Calculate area of a hollow circle.
For solid circles, use inner_radius=0.
Args:
inner_radius: Inner radius (0 for solid circle)
outer_radius: Outer radius
Returns:
Area of the hollow circle shape
Example:
solid_area = area_for_circle(0, 25) # Solid circle, radius 25
ring_area = area_for_circle(20, 25) # Ring from radius 20 to 25
"""
def area_for_segment(
a: tuple[float, float],
b: tuple[float, float],
radius: float
) -> float:
"""
Calculate area of a beveled segment.
Will always be zero if radius is zero (line has no area).
Args:
a: Start point of the segment
b: End point of the segment
radius: Radius/thickness of the segment
Returns:
Area of the beveled segment
Example:
# Thick line segment from (0,0) to (100,0) with radius 5
area = area_for_segment((0, 0), (100, 0), 5)
"""
def area_for_poly(
vertices: Sequence[tuple[float, float]],
radius: float = 0
) -> float:
"""
Calculate signed area of a polygon shape.
Returns negative number for polygons with clockwise winding.
Args:
vertices: List of vertex coordinates
radius: Optional radius for rounded corners
Returns:
Signed area (negative for clockwise polygons)
Example:
# Rectangle vertices (counter-clockwise)
vertices = [(0, 0), (50, 0), (50, 30), (0, 30)]
area = area_for_poly(vertices) # Positive area: 1500.0
# Same vertices clockwise
vertices_cw = [(0, 0), (0, 30), (50, 30), (50, 0)]
area_cw = area_for_poly(vertices_cw) # Negative area: -1500.0
"""Pymunk provides functions to calculate moments of inertia for different shape types, essential for realistic physics simulations.
def moment_for_circle(
mass: float,
inner_radius: float,
outer_radius: float,
offset: tuple[float, float] = (0, 0)
) -> float:
"""
Calculate moment of inertia for a hollow circle.
A solid circle has an inner radius of 0.
Args:
mass: Mass of the shape
inner_radius: Inner radius (0 for solid circle)
outer_radius: Outer radius
offset: Offset from body center (default: (0, 0))
Returns:
Moment of inertia value
Example:
# Solid disk
solid_moment = moment_for_circle(mass=10, inner_radius=0, outer_radius=25)
# Ring/hollow circle
ring_moment = moment_for_circle(mass=10, inner_radius=20, outer_radius=25)
# Off-center circle
offset_moment = moment_for_circle(mass=10, inner_radius=0, outer_radius=15, offset=(10, 5))
"""
def moment_for_segment(
mass: float,
a: tuple[float, float],
b: tuple[float, float],
radius: float
) -> float:
"""
Calculate moment of inertia for a line segment.
The endpoints a and b are relative to the body center.
Args:
mass: Mass of the segment
a: Start point relative to body
b: End point relative to body
radius: Thickness radius of the segment
Returns:
Moment of inertia value
Example:
# Horizontal line segment
moment = moment_for_segment(mass=5, a=(0, 0), b=(100, 0), radius=2)
# Vertical line segment
moment = moment_for_segment(mass=5, a=(0, -50), b=(0, 50), radius=3)
"""
def moment_for_box(mass: float, size: tuple[float, float]) -> float:
"""
Calculate moment of inertia for a solid box centered on the body.
Args:
mass: Mass of the box
size: (width, height) of the box
Returns:
Moment of inertia value
Example:
# Square box 50x50
moment = moment_for_box(mass=10, size=(50, 50))
# Rectangular box 100x30
moment = moment_for_box(mass=15, size=(100, 30))
"""
def moment_for_poly(
mass: float,
vertices: Sequence[tuple[float, float]],
offset: tuple[float, float] = (0, 0),
radius: float = 0
) -> float:
"""
Calculate moment of inertia for a solid polygon shape.
Assumes the polygon center of gravity is at its centroid.
The offset is added to each vertex.
Args:
mass: Mass of the polygon
vertices: List of vertex coordinates
offset: Offset added to each vertex (default: (0, 0))
radius: Optional radius for rounded corners
Returns:
Moment of inertia value
Example:
# Triangle
triangle_vertices = [(0, 0), (50, 0), (25, 43)]
moment = moment_for_poly(mass=8, vertices=triangle_vertices)
# Rectangle with offset
rect_vertices = [(0, 0), (40, 0), (40, 20), (0, 20)]
moment = moment_for_poly(mass=12, vertices=rect_vertices, offset=(10, 5))
# Rounded polygon
moment = moment_for_poly(mass=10, vertices=rect_vertices, radius=3)
"""def empty_callback(*args: Any, **kwargs: Any) -> None:
"""
A default empty callback function.
Can be used to reset a collision callback to its original empty
function. More efficient than defining your own empty/do-nothing method.
Args:
*args: Any positional arguments (ignored)
**kwargs: Any keyword arguments (ignored)
Example:
# Reset collision handler to do nothing
handler = space.add_collision_handler(1, 2)
handler.begin = empty_callback
handler.pre_solve = empty_callback
# Or use directly as a placeholder
space.add_collision_handler(3, 4).begin = empty_callback
"""import pymunk
# Calculate moments of inertia for different shapes
mass = 10
# Circle (solid disk)
circle_moment = pymunk.moment_for_circle(mass, 0, 25) # inner_radius=0, outer_radius=25
# Hollow circle (ring)
ring_moment = pymunk.moment_for_circle(mass, 20, 25) # inner_radius=20, outer_radius=25
# Box
box_moment = pymunk.moment_for_box(mass, (50, 30)) # width=50, height=30
# Polygon
vertices = [(0, 0), (50, 0), (50, 30), (0, 30)]
poly_moment = pymunk.moment_for_poly(mass, vertices)
# Line segment
segment_moment = pymunk.moment_for_segment(mass, (0, 0), (100, 0), radius=5)
# Compound shape (multiple shapes on same body)
def create_compound_body():
# Let pymunk calculate total mass/moment from shapes
body = pymunk.Body()
# Add multiple shapes with different densities
circle = pymunk.Circle(body, 25, (0, 0))
circle.density = 1.0
box = pymunk.Poly.create_box(body, (40, 20), radius=2)
box.density = 2.0 # Denser material
return body, [circle, box]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.
Install with Tessl CLI
npx tessl i tessl/pypi-pymunk