CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pymunk

Pymunk is a easy-to-use pythonic 2D physics library built on Munk2D

Pending
Overview
Eval results
Files

utilities.mddocs/

Mathematical Utilities

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.

Vec2d - 2D Vector Class

The Vec2d class is an immutable 2D vector with extensive mathematical operations and geometric functions.

Class Definition

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)
        """

Properties

# 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"""

Arithmetic Operations

# 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)

Vector Operations

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
    """

Rotation Operations

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."""

Dot and Cross Products

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)
    """

Static Factory Methods

@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 and Conversion

# 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)"

BB - Bounding Box Class

Axis-aligned bounding box for efficient spatial operations and collision detection.

Class Definition

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)

Factory Methods

@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)
    """

Query Operations

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
    """

Geometric Operations

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."""

Advanced Operations

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.
    """

Transform - 2D Transformation Matrix

Represents 2D affine transformations for scaling, rotation, translation, and skewing operations.

Class Definition

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

Static Factory Methods

@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)
    """

Transform Operations

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°
    """

ShapeFilter - Collision Filtering

Controls which shapes can collide with each other using groups, categories, and masks.

Class Definition

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.
    """

Static Methods

@staticmethod
def ALL_CATEGORIES() -> int:
    """Return bitmask with all category bits set (0xFFFFFFFF)"""

@staticmethod  
def ALL_MASKS() -> int:
    """Return bitmask matching all categories (0xFFFFFFFF)"""

Collision Logic

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
    """

Usage Examples

Vector Mathematics

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 product

Polar Coordinates

import 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}")

Bounding Box Operations

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}")

Transform Compositions

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_pos

Collision Filtering System

import 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
)

Area Calculation Functions

Pymunk provides utilities for calculating areas of geometric shapes, useful for physics calculations and shape analysis.

Area Functions

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
    """

Moment of Inertia Calculation Functions

Pymunk provides functions to calculate moments of inertia for different shape types, essential for realistic physics simulations.

Moment Functions

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)
    """

Utility Callback Functions

Empty Callback Function

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
    """

Moment of Inertia Calculations

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

docs

bodies-shapes.md

constraints.md

geometry.md

index.md

physics-world.md

utilities.md

visualization.md

tile.json