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

bodies-shapes.mddocs/

Bodies and Shapes

Bodies represent rigid objects with mass, position, velocity, and rotation, while Shapes define the collision geometry and material properties attached to bodies. Together they form the fundamental components of physics simulation.

Body Class

The Body class represents a rigid body with physical properties like mass, moment of inertia, position, velocity, and rotation.

Class Definition and Body Types

class Body:
    """
    A rigid body with mass, position, velocity and rotation.
    
    Bodies can be copied and pickled. Sleeping bodies wake up in the copy.
    When copied, spaces, shapes or constraints attached to the body are not copied.
    """
    
    # Body type constants
    DYNAMIC: int     # Normal simulated bodies affected by forces 
    KINEMATIC: int   # User-controlled bodies with infinite mass
    STATIC: int      # Non-moving bodies (usually terrain)
    
    def __init__(
        self, 
        mass: float = 0, 
        moment: float = 0, 
        body_type: int = DYNAMIC
    ) -> None:
        """
        Create a new body.
        
        Args:
            mass: Body mass (must be > 0 for dynamic bodies in space)
            moment: Moment of inertia (must be > 0 for dynamic bodies in space)
            body_type: DYNAMIC, KINEMATIC, or STATIC
        """

Body Types Explained

# Dynamic bodies - normal physics objects
body = pymunk.Body(mass=10, moment=pymunk.moment_for_circle(10, 0, 25))
body.body_type == pymunk.Body.DYNAMIC  # True
# - Affected by gravity, forces, and collisions
# - Have finite mass and can be moved by physics
# - Most game objects (balls, boxes, characters)

# Kinematic bodies - user-controlled objects  
kinematic_body = pymunk.Body(body_type=pymunk.Body.KINEMATIC)
# - Controlled by setting velocity, not affected by forces
# - Have infinite mass, don't respond to collisions
# - Good for moving platforms, elevators, doors
# - Objects touching kinematic bodies cannot fall asleep

# Static bodies - immovable terrain
static_body = pymunk.Body(body_type=pymunk.Body.STATIC)  
# Or use space.static_body
# - Never move (except manual repositioning)  
# - Infinite mass, provide collision boundaries
# - Optimized for performance (no collision checks with other static bodies)
# - Ground, walls, level geometry

Core Properties

# Identity
body.id: int
"""Unique identifier for the body (changes on copy/pickle)"""

# Physical properties
body.mass: float
"""Mass of the body (must be > 0 for dynamic bodies in space)"""

body.moment: float  
"""
Moment of inertia - rotational mass.
Can be set to float('inf') to prevent rotation.
"""

body.body_type: int
"""Body type: DYNAMIC, KINEMATIC, or STATIC"""

# Position and orientation
body.position: Vec2d
"""Position in world coordinates. Call space.reindex_shapes_for_body() after manual changes."""

body.center_of_gravity: Vec2d = Vec2d(0, 0)
"""Center of gravity offset in body local coordinates"""

body.angle: float
"""Rotation angle in radians. Body rotates around center of gravity."""

body.rotation_vector: Vec2d
"""Unit vector representing current rotation (readonly)"""

# Motion properties
body.velocity: Vec2d
"""Linear velocity of center of gravity"""

body.angular_velocity: float  
"""Angular velocity in radians per second"""

# Force accumulation (reset each timestep)
body.force: Vec2d
"""Force applied to center of gravity (manual forces only)"""

body.torque: float
"""Torque applied to the body (manual torques only)"""

Derived Properties

body.kinetic_energy: float
"""Current kinetic energy of the body (readonly)"""

body.is_sleeping: bool
"""Whether the body is currently sleeping for optimization (readonly)"""

body.space: Optional[Space]
"""Space the body belongs to, or None (readonly)"""

body.shapes: KeysView[Shape]
"""View of all shapes attached to this body (readonly)"""

body.constraints: KeysView[Constraint]  
"""View of all constraints attached to this body (readonly)"""

Force and Impulse Application

def apply_force_at_world_point(
    self, 
    force: tuple[float, float], 
    point: tuple[float, float]
) -> None:
    """
    Apply force as if applied from world point.
    
    Forces are accumulated and applied during physics step.
    Use for continuous forces like thrusters or wind.
    
    Args:
        force: Force vector in Newtons
        point: World position where force is applied
        
    Example:
        # Apply upward thrust at right side of body
        body.apply_force_at_world_point((0, 1000), body.position + (50, 0))
    """

def apply_force_at_local_point(
    self,
    force: tuple[float, float], 
    point: tuple[float, float] = (0, 0)
) -> None:
    """
    Apply force as if applied from body local point.
    
    Args:
        force: Force vector in body coordinates
        point: Local position where force is applied (default center)
    """

def apply_impulse_at_world_point(
    self,
    impulse: tuple[float, float],
    point: tuple[float, float] 
) -> None:
    """
    Apply impulse as if applied from world point.
    
    Impulses cause immediate velocity changes.
    Use for instantaneous events like explosions or collisions.
    
    Args:
        impulse: Impulse vector (force * time)
        point: World position where impulse is applied
        
    Example:
        # Explosion impulse
        direction = target_pos - explosion_center
        impulse = direction.normalized() * 500
        body.apply_impulse_at_world_point(impulse, explosion_center)
    """

def apply_impulse_at_local_point(
    self,
    impulse: tuple[float, float],
    point: tuple[float, float] = (0, 0)
) -> None:
    """Apply impulse as if applied from body local point."""

Coordinate Transformations

def local_to_world(self, v: tuple[float, float]) -> Vec2d:
    """
    Convert body local coordinates to world space.
    
    Local coordinates have (0,0) at center of gravity and rotate with body.
    
    Args:
        v: Vector in body local coordinates
        
    Returns:
        Vector in world coordinates
        
    Example:
        # Get world position of point on body
        local_point = (25, 0)  # 25 units right of center
        world_point = body.local_to_world(local_point)
    """

def world_to_local(self, v: tuple[float, float]) -> Vec2d:
    """
    Convert world space coordinates to body local coordinates.
    
    Args:
        v: Vector in world coordinates
        
    Returns:
        Vector in body local coordinates
    """

def velocity_at_world_point(self, point: tuple[float, float]) -> Vec2d:
    """
    Get velocity at a world point on the body.
    
    Accounts for both linear and angular velocity.
    Useful for surface velocity calculations.
    
    Args:
        point: World position
        
    Returns:
        Velocity at that point
        
    Example:
        # Get velocity at edge of rotating wheel
        edge_point = body.position + (wheel_radius, 0)  
        edge_velocity = body.velocity_at_world_point(edge_point)
    """

def velocity_at_local_point(self, point: tuple[float, float]) -> Vec2d:
    """Get velocity at a body local point."""

Sleep Management

def activate(self) -> None:
    """Wake up a sleeping body."""

def sleep(self) -> None:
    """Force body to sleep (optimization for inactive bodies)."""
    
def sleep_with_group(self, body: 'Body') -> None:
    """
    Sleep this body together with another body.
    
    Bodies in the same sleep group wake up together when one is disturbed.
    Useful for connected objects like a stack of boxes.
    """

Custom Integration Functions

body.velocity_func: Callable[[Body, Vec2d, float, float], None]
"""
Custom velocity integration function called each timestep.

Function signature: velocity_func(body, gravity, damping, dt)
Override to implement custom gravity, damping, or other effects per body.

Example:
    def custom_gravity(body, gravity, damping, dt):
        # Custom gravity based on body position
        if body.position.y > 500:
            gravity = Vec2d(0, -500)  # Weaker gravity at height
        pymunk.Body.update_velocity(body, gravity, damping, dt)
    
    body.velocity_func = custom_gravity
"""

body.position_func: Callable[[Body, float], None] 
"""
Custom position integration function called each timestep.

Function signature: position_func(body, dt)  
Override to implement custom movement behavior.
"""

Shape Base Class

Shapes define collision geometry and material properties. All shapes inherit from the base Shape class.

Base Shape Properties

class Shape:
    """
    Base class for all collision shapes.
    
    Shapes can be copied and pickled. The attached body is also copied.
    """

# Physical properties
shape.mass: float = 0
"""
Mass of the shape for automatic body mass calculation.
Alternative to setting body mass directly.
"""

shape.density: float = 0  
"""
Density (mass per unit area) for automatic mass calculation.
More intuitive than setting mass directly.
"""

shape.moment: float
"""Moment of inertia contribution from this shape (readonly)"""

shape.area: float
"""Area of the shape (readonly)"""

shape.center_of_gravity: Vec2d
"""Center of gravity of the shape (readonly)"""

# Material properties
shape.friction: float = 0.7
"""
Friction coefficient (0 = frictionless, higher = more friction).
Combined with other shape's friction during collision.
"""

shape.elasticity: float = 0.0
"""
Bounce/restitution coefficient (0 = no bounce, 1 = perfect bounce).
Combined with other shape's elasticity during collision.
"""

shape.surface_velocity: Vec2d = Vec2d(0, 0)
"""
Surface velocity for conveyor belt effects.
Only affects friction calculation, not collision resolution.
"""

# Collision properties  
shape.collision_type: int = 0
"""Collision type identifier for callback filtering"""

shape.filter: ShapeFilter = ShapeFilter()
"""Collision filter controlling which shapes can collide"""

shape.sensor: bool = False
"""
If True, shape detects collisions but doesn't respond physically.
Useful for trigger areas, pickups, detection zones.
"""

# Relationships
shape.body: Optional[Body]
"""Body this shape is attached to (can be None)"""

shape.space: Optional[Space]
"""Space this shape belongs to (readonly)"""

shape.bb: BB
"""Cached bounding box - valid after cache_bb() or space.step()"""

Shape Query Methods

def point_query(self, p: tuple[float, float]) -> PointQueryInfo:
    """
    Test if point lies within shape.
    
    Args:
        p: Point to test
        
    Returns:
        PointQueryInfo with distance (negative if inside), closest point, etc.
    """

def segment_query(
    self, 
    start: tuple[float, float], 
    end: tuple[float, float], 
    radius: float = 0
) -> Optional[SegmentQueryInfo]:
    """
    Test line segment intersection with shape.
    
    Returns:
        SegmentQueryInfo if intersected, None otherwise
    """

def shapes_collide(self, other: 'Shape') -> ContactPointSet:
    """
    Get collision information between two shapes.
    
    Returns:
        ContactPointSet with contact points and normal
    """

def cache_bb(self) -> BB:
    """Update and return shape's bounding box"""

def update(self, transform: Transform) -> BB:
    """Update shape with explicit transform (for unattached shapes)"""

Circle Shape

class Circle(Shape):
    """
    Circular collision shape - fastest and simplest collision shape.
    
    Perfect for balls, wheels, coins, circular objects.
    """
    
    def __init__(
        self,
        body: Optional[Body],
        radius: float, 
        offset: tuple[float, float] = (0, 0)
    ) -> None:
        """
        Create a circle shape.
        
        Args:
            body: Body to attach to (can be None)
            radius: Circle radius
            offset: Offset from body center of gravity
            
        Example:
            # Circle at body center
            circle = pymunk.Circle(body, 25)
            
            # Circle offset from body center  
            circle = pymunk.Circle(body, 15, offset=(10, 5))
        """
    
    # Properties
    radius: float
    """Radius of the circle"""
    
    offset: Vec2d  
    """Offset from body center of gravity"""
    
    # Unsafe modification (use carefully during simulation)
    def unsafe_set_radius(self, radius: float) -> None:
        """Change radius during simulation (may cause instability)"""
        
    def unsafe_set_offset(self, offset: tuple[float, float]) -> None:
        """Change offset during simulation (may cause instability)"""

Segment Shape

class Segment(Shape):
    """
    Line segment collision shape with thickness.
    
    Mainly for static shapes like walls, floors, ramps.
    Can be beveled for thickness.
    """
    
    def __init__(
        self,
        body: Optional[Body],
        a: tuple[float, float],
        b: tuple[float, float], 
        radius: float
    ) -> None:
        """
        Create a line segment shape.
        
        Args:
            body: Body to attach to (can be None)
            a: First endpoint
            b: Second endpoint  
            radius: Thickness radius (0 for infinitely thin)
            
        Example:
            # Ground segment
            ground = pymunk.Segment(static_body, (0, 0), (800, 0), 5)
            
            # Sloped platform
            ramp = pymunk.Segment(body, (0, 0), (100, 50), 3)
        """
    
    # Properties
    a: Vec2d
    """First endpoint of the segment"""
    
    b: Vec2d
    """Second endpoint of the segment"""
    
    normal: Vec2d  
    """Normal vector of the segment (readonly)"""
    
    radius: float
    """Thickness radius of the segment"""
    
    # Unsafe modification
    def unsafe_set_endpoints(
        self, 
        a: tuple[float, float], 
        b: tuple[float, float]
    ) -> None:
        """Change endpoints during simulation (may cause instability)"""
        
    def unsafe_set_radius(self, radius: float) -> None:
        """Change radius during simulation (may cause instability)"""
        
    def set_neighbors(
        self, 
        prev: tuple[float, float], 
        next: tuple[float, float]
    ) -> None:
        """
        Set neighboring segments for smooth collision.
        
        Prevents objects from catching on segment joints.
        
        Args:
            prev: Previous segment endpoint
            next: Next segment endpoint
        """

Polygon Shape

class Poly(Shape):
    """
    Convex polygon collision shape.
    
    Most flexible but slowest collision shape.
    Automatically converts concave polygons to convex hull.
    """
    
    def __init__(
        self,
        body: Optional[Body],
        vertices: Sequence[tuple[float, float]],
        transform: Optional[Transform] = None,
        radius: float = 0
    ) -> None:
        """
        Create a polygon shape.
        
        Args:
            body: Body to attach to (can be None)
            vertices: List of vertices in counter-clockwise order
            transform: Optional transform applied to vertices  
            radius: Corner rounding radius (reduces catching on edges)
            
        Note:
            Vertices should be centered around (0,0) or use transform.
            Concave polygons are automatically converted to convex hull.
            
        Example:
            # Box polygon
            size = (50, 30)
            vertices = [
                (-size[0]/2, -size[1]/2),
                (size[0]/2, -size[1]/2), 
                (size[0]/2, size[1]/2),
                (-size[0]/2, size[1]/2)
            ]
            poly = pymunk.Poly(body, vertices)
            
            # Triangle
            vertices = [(0, 20), (-20, -20), (20, -20)]
            triangle = pymunk.Poly(body, vertices, radius=2)
        """
    
    # Properties  
    radius: float
    """Corner rounding radius"""
    
    # Methods
    def get_vertices(self) -> list[Vec2d]:
        """Get list of vertices in world coordinates"""
        
    def unsafe_set_vertices(
        self, 
        vertices: Sequence[tuple[float, float]], 
        transform: Optional[Transform] = None
    ) -> None:
        """Change vertices during simulation (may cause instability)"""
        
    def unsafe_set_radius(self, radius: float) -> None:
        """Change radius during simulation (may cause instability)"""
    
    # Static factory methods
    @staticmethod  
    def create_box(
        body: Optional[Body], 
        size: tuple[float, float], 
        radius: float = 0
    ) -> 'Poly':
        """
        Create a box polygon.
        
        Args:
            body: Body to attach to
            size: (width, height) of box
            radius: Corner rounding radius
            
        Example:
            box = pymunk.Poly.create_box(body, (60, 40), radius=2)
        """
        
    @staticmethod
    def create_box_bb(
        body: Optional[Body],
        bb: BB, 
        radius: float = 0
    ) -> 'Poly':
        """
        Create box polygon from bounding box.
        
        Args:
            body: Body to attach to
            bb: Bounding box defining box shape
            radius: Corner rounding radius
        """

Usage Examples

Basic Rigid Bodies

import pymunk
import math

# Create physics space
space = pymunk.Space()
space.gravity = (0, -981)

# Static ground
ground_body = space.static_body
ground = pymunk.Segment(ground_body, (0, 0), (800, 0), 5)
ground.friction = 0.7
space.add(ground)

# Dynamic ball
ball_mass = 10
ball_radius = 25
ball_moment = pymunk.moment_for_circle(ball_mass, 0, ball_radius)

ball_body = pymunk.Body(ball_mass, ball_moment)
ball_body.position = 400, 300
ball_shape = pymunk.Circle(ball_body, ball_radius)
ball_shape.friction = 0.7
ball_shape.elasticity = 0.9  # Bouncy

space.add(ball_body, ball_shape)

# Dynamic box
box_mass = 15
box_size = (40, 60)
box_moment = pymunk.moment_for_box(box_mass, box_size)

box_body = pymunk.Body(box_mass, box_moment)
box_body.position = 200, 400
box_shape = pymunk.Poly.create_box(box_body, box_size)
box_shape.friction = 0.5

space.add(box_body, box_shape)

Material Properties

import pymunk

# Different material types
def create_material_shapes(body):
    # Ice - slippery, bouncy
    ice_shape = pymunk.Circle(body, 20)
    ice_shape.friction = 0.1      # Very slippery
    ice_shape.elasticity = 0.8    # Quite bouncy
    
    # Rubber - grippy, very bouncy
    rubber_shape = pymunk.Circle(body, 20) 
    rubber_shape.friction = 1.2   # Very grippy
    rubber_shape.elasticity = 0.95 # Almost perfect bounce
    
    # Metal - medium friction, little bounce
    metal_shape = pymunk.Circle(body, 20)
    metal_shape.friction = 0.7    # Moderate friction
    metal_shape.elasticity = 0.2  # Little bounce
    
    # Wood - medium properties
    wood_shape = pymunk.Circle(body, 20)
    wood_shape.friction = 0.4     # Some friction
    wood_shape.elasticity = 0.3   # Some bounce

# Conveyor belt effect
conveyor = pymunk.Segment(static_body, (100, 50), (300, 50), 5)
conveyor.surface_velocity = (100, 0)  # Move objects rightward
conveyor.friction = 0.8  # Need friction for surface velocity to work

Mass and Density

import pymunk

# Method 1: Set body mass directly
mass = 10
moment = pymunk.moment_for_circle(mass, 0, 25) 
body = pymunk.Body(mass, moment)
circle = pymunk.Circle(body, 25)

# Method 2: Let shapes calculate mass automatically
body = pymunk.Body()  # No mass/moment specified

# Set shape density
circle = pymunk.Circle(body, 25)
circle.density = 0.1  # Light material

poly = pymunk.Poly.create_box(body, (50, 50))
poly.density = 0.2    # Heavier material

# Body mass/moment calculated automatically from shapes
space.add(body, circle, poly)

# Method 3: Set shape mass directly  
circle = pymunk.Circle(body, 25)
circle.mass = 5  # 5 units of mass

poly = pymunk.Poly.create_box(body, (50, 50))
poly.mass = 15   # 15 units of mass
# Total body mass will be 20

Force Application

import pymunk
import math

space = pymunk.Space()
body = pymunk.Body(10, pymunk.moment_for_circle(10, 0, 25))
body.position = 400, 300

# Continuous forces (applied each frame)
def apply_thrust():
    # Rocket thrust at back of ship
    thrust_force = (0, 1000)  # Upward thrust
    thrust_point = body.position + (0, -25)  # Back of ship
    body.apply_force_at_world_point(thrust_force, thrust_point)

# Impulse forces (one-time application)  
def explosion(explosion_center, strength):
    direction = body.position - explosion_center
    distance = abs(direction)
    if distance > 0:
        # Impulse falls off with distance
        impulse_magnitude = strength / (distance * distance)
        impulse = direction.normalized() * impulse_magnitude
        body.apply_impulse_at_world_point(impulse, explosion_center)

# Torque for rotation
body.torque = 500  # Spin clockwise

# Local force application
# Force applied at local point creates both linear and angular motion
local_force = (100, 0)   # Rightward force
local_point = (0, 25)    # Top of object
body.apply_force_at_local_point(local_force, local_point)

Kinematic Bodies (Moving Platforms)

import pymunk
import math

# Create kinematic platform
platform_body = pymunk.Body(body_type=pymunk.Body.KINEMATIC)
platform_shape = pymunk.Poly.create_box(platform_body, (100, 20))
platform_body.position = 200, 100

space.add(platform_body, platform_shape)

# Control kinematic body motion
def update_platform(dt):
    # Sine wave motion
    time = space.current_time_step
    
    # Horizontal oscillation
    platform_body.velocity = (50 * math.cos(time), 0)
    
    # Or set position directly (less stable)
    # new_x = 200 + 50 * math.sin(time) 
    # platform_body.position = new_x, 100
    # space.reindex_shapes_for_body(platform_body)

# Moving elevator
elevator_body = pymunk.Body(body_type=pymunk.Body.KINEMATIC)
elevator_shape = pymunk.Poly.create_box(elevator_body, (80, 15))

def move_elevator_up():
    elevator_body.velocity = (0, 100)  # Move up

def move_elevator_down():
    elevator_body.velocity = (0, -100)  # Move down
    
def stop_elevator():
    elevator_body.velocity = (0, 0)    # Stop

Sensors and Triggers

import pymunk

# Create sensor shape for trigger areas
trigger_body = space.static_body
trigger_shape = pymunk.Circle(trigger_body, 50, (400, 300))
trigger_shape.sensor = True  # No physical collision response
trigger_shape.collision_type = TRIGGER_TYPE

# Player shape
player_body = pymunk.Body(1, pymunk.moment_for_circle(1, 0, 15))
player_shape = pymunk.Circle(player_body, 15)
player_shape.collision_type = PLAYER_TYPE

def trigger_callback(arbiter, space, data):
    """Called when player enters/exits trigger area"""
    trigger_shape, player_shape = arbiter.shapes
    print("Player in trigger area!")
    return False  # Don't process as physical collision

space.on_collision(TRIGGER_TYPE, PLAYER_TYPE, begin=trigger_callback)

# Pickup items (remove on contact)
pickup_body = space.static_body
pickup_shape = pymunk.Circle(pickup_body, 10, (300, 200))
pickup_shape.sensor = True
pickup_shape.collision_type = PICKUP_TYPE

def pickup_callback(arbiter, space, data):
    pickup_shape, player_shape = arbiter.shapes
    # Schedule removal after step completes
    space.add_post_step_callback(remove_pickup, pickup_shape, pickup_shape)
    return False

def remove_pickup(space, shape):
    space.remove(shape)
    print("Collected pickup!")

Shape Queries and Collision Detection

import pymunk

# Test if point is inside shape
test_point = (100, 200)
query_result = shape.point_query(test_point)
if query_result.distance < 0:
    print("Point is inside shape!")
    print(f"Distance to surface: {abs(query_result.distance)}")

# Line of sight / raycast using segment query
start = player_body.position
end = enemy_body.position
line_query = shape.segment_query(start, end, radius=0)
if line_query:
    print(f"Line blocked at {line_query.point}")
    print(f"Surface normal: {line_query.normal}")
else:
    print("Clear line of sight")

# Shape vs shape collision test
collision_info = shape1.shapes_collide(shape2)
if collision_info.points:
    print("Shapes are colliding!")
    for point in collision_info.points:
        print(f"Contact at {point.point_a}")

Bodies and shapes provide the foundation for realistic physics simulation with intuitive APIs for mass properties, material behavior, force application, and collision detection suitable for games, simulations, and interactive 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