or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

bodies-shapes.mdconstraints.mdgeometry.mdindex.mdphysics-world.mdutilities.mdvisualization.md

bodies-shapes.mddocs/

0

# Bodies and Shapes

1

2

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.

3

4

## Body Class

5

6

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

7

8

### Class Definition and Body Types

9

10

```python { .api }

11

class Body:

12

"""

13

A rigid body with mass, position, velocity and rotation.

14

15

Bodies can be copied and pickled. Sleeping bodies wake up in the copy.

16

When copied, spaces, shapes or constraints attached to the body are not copied.

17

"""

18

19

# Body type constants

20

DYNAMIC: int # Normal simulated bodies affected by forces

21

KINEMATIC: int # User-controlled bodies with infinite mass

22

STATIC: int # Non-moving bodies (usually terrain)

23

24

def __init__(

25

self,

26

mass: float = 0,

27

moment: float = 0,

28

body_type: int = DYNAMIC

29

) -> None:

30

"""

31

Create a new body.

32

33

Args:

34

mass: Body mass (must be > 0 for dynamic bodies in space)

35

moment: Moment of inertia (must be > 0 for dynamic bodies in space)

36

body_type: DYNAMIC, KINEMATIC, or STATIC

37

"""

38

```

39

40

### Body Types Explained

41

42

```python { .api }

43

# Dynamic bodies - normal physics objects

44

body = pymunk.Body(mass=10, moment=pymunk.moment_for_circle(10, 0, 25))

45

body.body_type == pymunk.Body.DYNAMIC # True

46

# - Affected by gravity, forces, and collisions

47

# - Have finite mass and can be moved by physics

48

# - Most game objects (balls, boxes, characters)

49

50

# Kinematic bodies - user-controlled objects

51

kinematic_body = pymunk.Body(body_type=pymunk.Body.KINEMATIC)

52

# - Controlled by setting velocity, not affected by forces

53

# - Have infinite mass, don't respond to collisions

54

# - Good for moving platforms, elevators, doors

55

# - Objects touching kinematic bodies cannot fall asleep

56

57

# Static bodies - immovable terrain

58

static_body = pymunk.Body(body_type=pymunk.Body.STATIC)

59

# Or use space.static_body

60

# - Never move (except manual repositioning)

61

# - Infinite mass, provide collision boundaries

62

# - Optimized for performance (no collision checks with other static bodies)

63

# - Ground, walls, level geometry

64

```

65

66

### Core Properties

67

68

```python { .api }

69

# Identity

70

body.id: int

71

"""Unique identifier for the body (changes on copy/pickle)"""

72

73

# Physical properties

74

body.mass: float

75

"""Mass of the body (must be > 0 for dynamic bodies in space)"""

76

77

body.moment: float

78

"""

79

Moment of inertia - rotational mass.

80

Can be set to float('inf') to prevent rotation.

81

"""

82

83

body.body_type: int

84

"""Body type: DYNAMIC, KINEMATIC, or STATIC"""

85

86

# Position and orientation

87

body.position: Vec2d

88

"""Position in world coordinates. Call space.reindex_shapes_for_body() after manual changes."""

89

90

body.center_of_gravity: Vec2d = Vec2d(0, 0)

91

"""Center of gravity offset in body local coordinates"""

92

93

body.angle: float

94

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

95

96

body.rotation_vector: Vec2d

97

"""Unit vector representing current rotation (readonly)"""

98

99

# Motion properties

100

body.velocity: Vec2d

101

"""Linear velocity of center of gravity"""

102

103

body.angular_velocity: float

104

"""Angular velocity in radians per second"""

105

106

# Force accumulation (reset each timestep)

107

body.force: Vec2d

108

"""Force applied to center of gravity (manual forces only)"""

109

110

body.torque: float

111

"""Torque applied to the body (manual torques only)"""

112

```

113

114

### Derived Properties

115

116

```python { .api }

117

body.kinetic_energy: float

118

"""Current kinetic energy of the body (readonly)"""

119

120

body.is_sleeping: bool

121

"""Whether the body is currently sleeping for optimization (readonly)"""

122

123

body.space: Optional[Space]

124

"""Space the body belongs to, or None (readonly)"""

125

126

body.shapes: KeysView[Shape]

127

"""View of all shapes attached to this body (readonly)"""

128

129

body.constraints: KeysView[Constraint]

130

"""View of all constraints attached to this body (readonly)"""

131

```

132

133

### Force and Impulse Application

134

135

```python { .api }

136

def apply_force_at_world_point(

137

self,

138

force: tuple[float, float],

139

point: tuple[float, float]

140

) -> None:

141

"""

142

Apply force as if applied from world point.

143

144

Forces are accumulated and applied during physics step.

145

Use for continuous forces like thrusters or wind.

146

147

Args:

148

force: Force vector in Newtons

149

point: World position where force is applied

150

151

Example:

152

# Apply upward thrust at right side of body

153

body.apply_force_at_world_point((0, 1000), body.position + (50, 0))

154

"""

155

156

def apply_force_at_local_point(

157

self,

158

force: tuple[float, float],

159

point: tuple[float, float] = (0, 0)

160

) -> None:

161

"""

162

Apply force as if applied from body local point.

163

164

Args:

165

force: Force vector in body coordinates

166

point: Local position where force is applied (default center)

167

"""

168

169

def apply_impulse_at_world_point(

170

self,

171

impulse: tuple[float, float],

172

point: tuple[float, float]

173

) -> None:

174

"""

175

Apply impulse as if applied from world point.

176

177

Impulses cause immediate velocity changes.

178

Use for instantaneous events like explosions or collisions.

179

180

Args:

181

impulse: Impulse vector (force * time)

182

point: World position where impulse is applied

183

184

Example:

185

# Explosion impulse

186

direction = target_pos - explosion_center

187

impulse = direction.normalized() * 500

188

body.apply_impulse_at_world_point(impulse, explosion_center)

189

"""

190

191

def apply_impulse_at_local_point(

192

self,

193

impulse: tuple[float, float],

194

point: tuple[float, float] = (0, 0)

195

) -> None:

196

"""Apply impulse as if applied from body local point."""

197

```

198

199

### Coordinate Transformations

200

201

```python { .api }

202

def local_to_world(self, v: tuple[float, float]) -> Vec2d:

203

"""

204

Convert body local coordinates to world space.

205

206

Local coordinates have (0,0) at center of gravity and rotate with body.

207

208

Args:

209

v: Vector in body local coordinates

210

211

Returns:

212

Vector in world coordinates

213

214

Example:

215

# Get world position of point on body

216

local_point = (25, 0) # 25 units right of center

217

world_point = body.local_to_world(local_point)

218

"""

219

220

def world_to_local(self, v: tuple[float, float]) -> Vec2d:

221

"""

222

Convert world space coordinates to body local coordinates.

223

224

Args:

225

v: Vector in world coordinates

226

227

Returns:

228

Vector in body local coordinates

229

"""

230

231

def velocity_at_world_point(self, point: tuple[float, float]) -> Vec2d:

232

"""

233

Get velocity at a world point on the body.

234

235

Accounts for both linear and angular velocity.

236

Useful for surface velocity calculations.

237

238

Args:

239

point: World position

240

241

Returns:

242

Velocity at that point

243

244

Example:

245

# Get velocity at edge of rotating wheel

246

edge_point = body.position + (wheel_radius, 0)

247

edge_velocity = body.velocity_at_world_point(edge_point)

248

"""

249

250

def velocity_at_local_point(self, point: tuple[float, float]) -> Vec2d:

251

"""Get velocity at a body local point."""

252

```

253

254

### Sleep Management

255

256

```python { .api }

257

def activate(self) -> None:

258

"""Wake up a sleeping body."""

259

260

def sleep(self) -> None:

261

"""Force body to sleep (optimization for inactive bodies)."""

262

263

def sleep_with_group(self, body: 'Body') -> None:

264

"""

265

Sleep this body together with another body.

266

267

Bodies in the same sleep group wake up together when one is disturbed.

268

Useful for connected objects like a stack of boxes.

269

"""

270

```

271

272

### Custom Integration Functions

273

274

```python { .api }

275

body.velocity_func: Callable[[Body, Vec2d, float, float], None]

276

"""

277

Custom velocity integration function called each timestep.

278

279

Function signature: velocity_func(body, gravity, damping, dt)

280

Override to implement custom gravity, damping, or other effects per body.

281

282

Example:

283

def custom_gravity(body, gravity, damping, dt):

284

# Custom gravity based on body position

285

if body.position.y > 500:

286

gravity = Vec2d(0, -500) # Weaker gravity at height

287

pymunk.Body.update_velocity(body, gravity, damping, dt)

288

289

body.velocity_func = custom_gravity

290

"""

291

292

body.position_func: Callable[[Body, float], None]

293

"""

294

Custom position integration function called each timestep.

295

296

Function signature: position_func(body, dt)

297

Override to implement custom movement behavior.

298

"""

299

```

300

301

## Shape Base Class

302

303

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

304

305

### Base Shape Properties

306

307

```python { .api }

308

class Shape:

309

"""

310

Base class for all collision shapes.

311

312

Shapes can be copied and pickled. The attached body is also copied.

313

"""

314

315

# Physical properties

316

shape.mass: float = 0

317

"""

318

Mass of the shape for automatic body mass calculation.

319

Alternative to setting body mass directly.

320

"""

321

322

shape.density: float = 0

323

"""

324

Density (mass per unit area) for automatic mass calculation.

325

More intuitive than setting mass directly.

326

"""

327

328

shape.moment: float

329

"""Moment of inertia contribution from this shape (readonly)"""

330

331

shape.area: float

332

"""Area of the shape (readonly)"""

333

334

shape.center_of_gravity: Vec2d

335

"""Center of gravity of the shape (readonly)"""

336

337

# Material properties

338

shape.friction: float = 0.7

339

"""

340

Friction coefficient (0 = frictionless, higher = more friction).

341

Combined with other shape's friction during collision.

342

"""

343

344

shape.elasticity: float = 0.0

345

"""

346

Bounce/restitution coefficient (0 = no bounce, 1 = perfect bounce).

347

Combined with other shape's elasticity during collision.

348

"""

349

350

shape.surface_velocity: Vec2d = Vec2d(0, 0)

351

"""

352

Surface velocity for conveyor belt effects.

353

Only affects friction calculation, not collision resolution.

354

"""

355

356

# Collision properties

357

shape.collision_type: int = 0

358

"""Collision type identifier for callback filtering"""

359

360

shape.filter: ShapeFilter = ShapeFilter()

361

"""Collision filter controlling which shapes can collide"""

362

363

shape.sensor: bool = False

364

"""

365

If True, shape detects collisions but doesn't respond physically.

366

Useful for trigger areas, pickups, detection zones.

367

"""

368

369

# Relationships

370

shape.body: Optional[Body]

371

"""Body this shape is attached to (can be None)"""

372

373

shape.space: Optional[Space]

374

"""Space this shape belongs to (readonly)"""

375

376

shape.bb: BB

377

"""Cached bounding box - valid after cache_bb() or space.step()"""

378

```

379

380

### Shape Query Methods

381

382

```python { .api }

383

def point_query(self, p: tuple[float, float]) -> PointQueryInfo:

384

"""

385

Test if point lies within shape.

386

387

Args:

388

p: Point to test

389

390

Returns:

391

PointQueryInfo with distance (negative if inside), closest point, etc.

392

"""

393

394

def segment_query(

395

self,

396

start: tuple[float, float],

397

end: tuple[float, float],

398

radius: float = 0

399

) -> Optional[SegmentQueryInfo]:

400

"""

401

Test line segment intersection with shape.

402

403

Returns:

404

SegmentQueryInfo if intersected, None otherwise

405

"""

406

407

def shapes_collide(self, other: 'Shape') -> ContactPointSet:

408

"""

409

Get collision information between two shapes.

410

411

Returns:

412

ContactPointSet with contact points and normal

413

"""

414

415

def cache_bb(self) -> BB:

416

"""Update and return shape's bounding box"""

417

418

def update(self, transform: Transform) -> BB:

419

"""Update shape with explicit transform (for unattached shapes)"""

420

```

421

422

## Circle Shape

423

424

```python { .api }

425

class Circle(Shape):

426

"""

427

Circular collision shape - fastest and simplest collision shape.

428

429

Perfect for balls, wheels, coins, circular objects.

430

"""

431

432

def __init__(

433

self,

434

body: Optional[Body],

435

radius: float,

436

offset: tuple[float, float] = (0, 0)

437

) -> None:

438

"""

439

Create a circle shape.

440

441

Args:

442

body: Body to attach to (can be None)

443

radius: Circle radius

444

offset: Offset from body center of gravity

445

446

Example:

447

# Circle at body center

448

circle = pymunk.Circle(body, 25)

449

450

# Circle offset from body center

451

circle = pymunk.Circle(body, 15, offset=(10, 5))

452

"""

453

454

# Properties

455

radius: float

456

"""Radius of the circle"""

457

458

offset: Vec2d

459

"""Offset from body center of gravity"""

460

461

# Unsafe modification (use carefully during simulation)

462

def unsafe_set_radius(self, radius: float) -> None:

463

"""Change radius during simulation (may cause instability)"""

464

465

def unsafe_set_offset(self, offset: tuple[float, float]) -> None:

466

"""Change offset during simulation (may cause instability)"""

467

```

468

469

## Segment Shape

470

471

```python { .api }

472

class Segment(Shape):

473

"""

474

Line segment collision shape with thickness.

475

476

Mainly for static shapes like walls, floors, ramps.

477

Can be beveled for thickness.

478

"""

479

480

def __init__(

481

self,

482

body: Optional[Body],

483

a: tuple[float, float],

484

b: tuple[float, float],

485

radius: float

486

) -> None:

487

"""

488

Create a line segment shape.

489

490

Args:

491

body: Body to attach to (can be None)

492

a: First endpoint

493

b: Second endpoint

494

radius: Thickness radius (0 for infinitely thin)

495

496

Example:

497

# Ground segment

498

ground = pymunk.Segment(static_body, (0, 0), (800, 0), 5)

499

500

# Sloped platform

501

ramp = pymunk.Segment(body, (0, 0), (100, 50), 3)

502

"""

503

504

# Properties

505

a: Vec2d

506

"""First endpoint of the segment"""

507

508

b: Vec2d

509

"""Second endpoint of the segment"""

510

511

normal: Vec2d

512

"""Normal vector of the segment (readonly)"""

513

514

radius: float

515

"""Thickness radius of the segment"""

516

517

# Unsafe modification

518

def unsafe_set_endpoints(

519

self,

520

a: tuple[float, float],

521

b: tuple[float, float]

522

) -> None:

523

"""Change endpoints during simulation (may cause instability)"""

524

525

def unsafe_set_radius(self, radius: float) -> None:

526

"""Change radius during simulation (may cause instability)"""

527

528

def set_neighbors(

529

self,

530

prev: tuple[float, float],

531

next: tuple[float, float]

532

) -> None:

533

"""

534

Set neighboring segments for smooth collision.

535

536

Prevents objects from catching on segment joints.

537

538

Args:

539

prev: Previous segment endpoint

540

next: Next segment endpoint

541

"""

542

```

543

544

## Polygon Shape

545

546

```python { .api }

547

class Poly(Shape):

548

"""

549

Convex polygon collision shape.

550

551

Most flexible but slowest collision shape.

552

Automatically converts concave polygons to convex hull.

553

"""

554

555

def __init__(

556

self,

557

body: Optional[Body],

558

vertices: Sequence[tuple[float, float]],

559

transform: Optional[Transform] = None,

560

radius: float = 0

561

) -> None:

562

"""

563

Create a polygon shape.

564

565

Args:

566

body: Body to attach to (can be None)

567

vertices: List of vertices in counter-clockwise order

568

transform: Optional transform applied to vertices

569

radius: Corner rounding radius (reduces catching on edges)

570

571

Note:

572

Vertices should be centered around (0,0) or use transform.

573

Concave polygons are automatically converted to convex hull.

574

575

Example:

576

# Box polygon

577

size = (50, 30)

578

vertices = [

579

(-size[0]/2, -size[1]/2),

580

(size[0]/2, -size[1]/2),

581

(size[0]/2, size[1]/2),

582

(-size[0]/2, size[1]/2)

583

]

584

poly = pymunk.Poly(body, vertices)

585

586

# Triangle

587

vertices = [(0, 20), (-20, -20), (20, -20)]

588

triangle = pymunk.Poly(body, vertices, radius=2)

589

"""

590

591

# Properties

592

radius: float

593

"""Corner rounding radius"""

594

595

# Methods

596

def get_vertices(self) -> list[Vec2d]:

597

"""Get list of vertices in world coordinates"""

598

599

def unsafe_set_vertices(

600

self,

601

vertices: Sequence[tuple[float, float]],

602

transform: Optional[Transform] = None

603

) -> None:

604

"""Change vertices during simulation (may cause instability)"""

605

606

def unsafe_set_radius(self, radius: float) -> None:

607

"""Change radius during simulation (may cause instability)"""

608

609

# Static factory methods

610

@staticmethod

611

def create_box(

612

body: Optional[Body],

613

size: tuple[float, float],

614

radius: float = 0

615

) -> 'Poly':

616

"""

617

Create a box polygon.

618

619

Args:

620

body: Body to attach to

621

size: (width, height) of box

622

radius: Corner rounding radius

623

624

Example:

625

box = pymunk.Poly.create_box(body, (60, 40), radius=2)

626

"""

627

628

@staticmethod

629

def create_box_bb(

630

body: Optional[Body],

631

bb: BB,

632

radius: float = 0

633

) -> 'Poly':

634

"""

635

Create box polygon from bounding box.

636

637

Args:

638

body: Body to attach to

639

bb: Bounding box defining box shape

640

radius: Corner rounding radius

641

"""

642

```

643

644

## Usage Examples

645

646

### Basic Rigid Bodies

647

648

```python { .api }

649

import pymunk

650

import math

651

652

# Create physics space

653

space = pymunk.Space()

654

space.gravity = (0, -981)

655

656

# Static ground

657

ground_body = space.static_body

658

ground = pymunk.Segment(ground_body, (0, 0), (800, 0), 5)

659

ground.friction = 0.7

660

space.add(ground)

661

662

# Dynamic ball

663

ball_mass = 10

664

ball_radius = 25

665

ball_moment = pymunk.moment_for_circle(ball_mass, 0, ball_radius)

666

667

ball_body = pymunk.Body(ball_mass, ball_moment)

668

ball_body.position = 400, 300

669

ball_shape = pymunk.Circle(ball_body, ball_radius)

670

ball_shape.friction = 0.7

671

ball_shape.elasticity = 0.9 # Bouncy

672

673

space.add(ball_body, ball_shape)

674

675

# Dynamic box

676

box_mass = 15

677

box_size = (40, 60)

678

box_moment = pymunk.moment_for_box(box_mass, box_size)

679

680

box_body = pymunk.Body(box_mass, box_moment)

681

box_body.position = 200, 400

682

box_shape = pymunk.Poly.create_box(box_body, box_size)

683

box_shape.friction = 0.5

684

685

space.add(box_body, box_shape)

686

```

687

688

### Material Properties

689

690

```python { .api }

691

import pymunk

692

693

# Different material types

694

def create_material_shapes(body):

695

# Ice - slippery, bouncy

696

ice_shape = pymunk.Circle(body, 20)

697

ice_shape.friction = 0.1 # Very slippery

698

ice_shape.elasticity = 0.8 # Quite bouncy

699

700

# Rubber - grippy, very bouncy

701

rubber_shape = pymunk.Circle(body, 20)

702

rubber_shape.friction = 1.2 # Very grippy

703

rubber_shape.elasticity = 0.95 # Almost perfect bounce

704

705

# Metal - medium friction, little bounce

706

metal_shape = pymunk.Circle(body, 20)

707

metal_shape.friction = 0.7 # Moderate friction

708

metal_shape.elasticity = 0.2 # Little bounce

709

710

# Wood - medium properties

711

wood_shape = pymunk.Circle(body, 20)

712

wood_shape.friction = 0.4 # Some friction

713

wood_shape.elasticity = 0.3 # Some bounce

714

715

# Conveyor belt effect

716

conveyor = pymunk.Segment(static_body, (100, 50), (300, 50), 5)

717

conveyor.surface_velocity = (100, 0) # Move objects rightward

718

conveyor.friction = 0.8 # Need friction for surface velocity to work

719

```

720

721

### Mass and Density

722

723

```python { .api }

724

import pymunk

725

726

# Method 1: Set body mass directly

727

mass = 10

728

moment = pymunk.moment_for_circle(mass, 0, 25)

729

body = pymunk.Body(mass, moment)

730

circle = pymunk.Circle(body, 25)

731

732

# Method 2: Let shapes calculate mass automatically

733

body = pymunk.Body() # No mass/moment specified

734

735

# Set shape density

736

circle = pymunk.Circle(body, 25)

737

circle.density = 0.1 # Light material

738

739

poly = pymunk.Poly.create_box(body, (50, 50))

740

poly.density = 0.2 # Heavier material

741

742

# Body mass/moment calculated automatically from shapes

743

space.add(body, circle, poly)

744

745

# Method 3: Set shape mass directly

746

circle = pymunk.Circle(body, 25)

747

circle.mass = 5 # 5 units of mass

748

749

poly = pymunk.Poly.create_box(body, (50, 50))

750

poly.mass = 15 # 15 units of mass

751

# Total body mass will be 20

752

```

753

754

### Force Application

755

756

```python { .api }

757

import pymunk

758

import math

759

760

space = pymunk.Space()

761

body = pymunk.Body(10, pymunk.moment_for_circle(10, 0, 25))

762

body.position = 400, 300

763

764

# Continuous forces (applied each frame)

765

def apply_thrust():

766

# Rocket thrust at back of ship

767

thrust_force = (0, 1000) # Upward thrust

768

thrust_point = body.position + (0, -25) # Back of ship

769

body.apply_force_at_world_point(thrust_force, thrust_point)

770

771

# Impulse forces (one-time application)

772

def explosion(explosion_center, strength):

773

direction = body.position - explosion_center

774

distance = abs(direction)

775

if distance > 0:

776

# Impulse falls off with distance

777

impulse_magnitude = strength / (distance * distance)

778

impulse = direction.normalized() * impulse_magnitude

779

body.apply_impulse_at_world_point(impulse, explosion_center)

780

781

# Torque for rotation

782

body.torque = 500 # Spin clockwise

783

784

# Local force application

785

# Force applied at local point creates both linear and angular motion

786

local_force = (100, 0) # Rightward force

787

local_point = (0, 25) # Top of object

788

body.apply_force_at_local_point(local_force, local_point)

789

```

790

791

### Kinematic Bodies (Moving Platforms)

792

793

```python { .api }

794

import pymunk

795

import math

796

797

# Create kinematic platform

798

platform_body = pymunk.Body(body_type=pymunk.Body.KINEMATIC)

799

platform_shape = pymunk.Poly.create_box(platform_body, (100, 20))

800

platform_body.position = 200, 100

801

802

space.add(platform_body, platform_shape)

803

804

# Control kinematic body motion

805

def update_platform(dt):

806

# Sine wave motion

807

time = space.current_time_step

808

809

# Horizontal oscillation

810

platform_body.velocity = (50 * math.cos(time), 0)

811

812

# Or set position directly (less stable)

813

# new_x = 200 + 50 * math.sin(time)

814

# platform_body.position = new_x, 100

815

# space.reindex_shapes_for_body(platform_body)

816

817

# Moving elevator

818

elevator_body = pymunk.Body(body_type=pymunk.Body.KINEMATIC)

819

elevator_shape = pymunk.Poly.create_box(elevator_body, (80, 15))

820

821

def move_elevator_up():

822

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

823

824

def move_elevator_down():

825

elevator_body.velocity = (0, -100) # Move down

826

827

def stop_elevator():

828

elevator_body.velocity = (0, 0) # Stop

829

```

830

831

### Sensors and Triggers

832

833

```python { .api }

834

import pymunk

835

836

# Create sensor shape for trigger areas

837

trigger_body = space.static_body

838

trigger_shape = pymunk.Circle(trigger_body, 50, (400, 300))

839

trigger_shape.sensor = True # No physical collision response

840

trigger_shape.collision_type = TRIGGER_TYPE

841

842

# Player shape

843

player_body = pymunk.Body(1, pymunk.moment_for_circle(1, 0, 15))

844

player_shape = pymunk.Circle(player_body, 15)

845

player_shape.collision_type = PLAYER_TYPE

846

847

def trigger_callback(arbiter, space, data):

848

"""Called when player enters/exits trigger area"""

849

trigger_shape, player_shape = arbiter.shapes

850

print("Player in trigger area!")

851

return False # Don't process as physical collision

852

853

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

854

855

# Pickup items (remove on contact)

856

pickup_body = space.static_body

857

pickup_shape = pymunk.Circle(pickup_body, 10, (300, 200))

858

pickup_shape.sensor = True

859

pickup_shape.collision_type = PICKUP_TYPE

860

861

def pickup_callback(arbiter, space, data):

862

pickup_shape, player_shape = arbiter.shapes

863

# Schedule removal after step completes

864

space.add_post_step_callback(remove_pickup, pickup_shape, pickup_shape)

865

return False

866

867

def remove_pickup(space, shape):

868

space.remove(shape)

869

print("Collected pickup!")

870

```

871

872

### Shape Queries and Collision Detection

873

874

```python { .api }

875

import pymunk

876

877

# Test if point is inside shape

878

test_point = (100, 200)

879

query_result = shape.point_query(test_point)

880

if query_result.distance < 0:

881

print("Point is inside shape!")

882

print(f"Distance to surface: {abs(query_result.distance)}")

883

884

# Line of sight / raycast using segment query

885

start = player_body.position

886

end = enemy_body.position

887

line_query = shape.segment_query(start, end, radius=0)

888

if line_query:

889

print(f"Line blocked at {line_query.point}")

890

print(f"Surface normal: {line_query.normal}")

891

else:

892

print("Clear line of sight")

893

894

# Shape vs shape collision test

895

collision_info = shape1.shapes_collide(shape2)

896

if collision_info.points:

897

print("Shapes are colliding!")

898

for point in collision_info.points:

899

print(f"Contact at {point.point_a}")

900

```

901

902

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.