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

physics-world.mddocs/

0

# Physics World Management (Space)

1

2

The `Space` class is the central physics world that contains and manages all bodies, shapes, and constraints. It handles collision detection, constraint solving, gravity, and time integration.

3

4

## Class Definition

5

6

```python { .api }

7

class Space:

8

"""

9

Spaces are the basic unit of simulation. You add rigid bodies, shapes

10

and joints to it and then step them all forward together through time.

11

12

A Space can be copied and pickled. Note that any post step callbacks are

13

not copied. Also note that some internal collision cache data is not copied,

14

which can make the simulation a bit unstable the first few steps of the

15

fresh copy.

16

"""

17

18

def __init__(self, threaded: bool = False) -> None:

19

"""

20

Create a new instance of the Space.

21

22

Args:

23

threaded: If True, enables multi-threaded simulation (not available on Windows).

24

Even with threaded=True, you must set Space.threads=2 to use more than one thread.

25

"""

26

```

27

28

## Core Properties

29

30

### Physics Configuration

31

32

```python { .api }

33

# Solver accuracy vs performance

34

space.iterations: int = 10

35

"""

36

Number of solver iterations per step. Higher values increase accuracy but use more CPU.

37

Default 10 is sufficient for most games.

38

"""

39

40

# Global forces

41

space.gravity: Vec2d = Vec2d(0, 0)

42

"""

43

Global gravity vector applied to all dynamic bodies.

44

Can be overridden per-body with custom velocity integration functions.

45

"""

46

47

space.damping: float = 1.0

48

"""

49

Global velocity damping factor. 0.9 means bodies lose 10% velocity per second.

50

1.0 = no damping. Can be overridden per-body.

51

"""

52

```

53

54

### Sleep Parameters

55

56

```python { .api }

57

space.idle_speed_threshold: float = 0

58

"""

59

Speed threshold for a body to be considered idle for sleeping.

60

Default 0 means space estimates based on gravity.

61

"""

62

63

space.sleep_time_threshold: float = float('inf')

64

"""

65

Time bodies must remain idle before falling asleep.

66

Default infinity disables sleeping. Set to enable sleeping optimization.

67

"""

68

```

69

70

### Collision Tuning

71

72

```python { .api }

73

space.collision_slop: float = 0.1

74

"""

75

Amount of overlap between shapes that is allowed for stability.

76

Set as high as possible without visible overlapping.

77

"""

78

79

space.collision_bias: float # ~0.002 (calculated)

80

"""

81

How fast overlapping shapes are pushed apart (0-1).

82

Controls percentage of overlap remaining after 1 second.

83

Rarely needs adjustment.

84

"""

85

86

space.collision_persistence: float = 3.0

87

"""

88

Number of frames collision solutions are cached to prevent jittering.

89

Rarely needs adjustment.

90

"""

91

```

92

93

### Threading

94

95

```python { .api }

96

space.threads: int = 1

97

"""

98

Number of threads for simulation (max 2, only if threaded=True).

99

Default 1 maintains determinism. Not supported on Windows.

100

"""

101

```

102

103

### Read-Only Properties

104

105

```python { .api }

106

space.current_time_step: float

107

"""Current or most recent timestep from Space.step()"""

108

109

space.static_body: Body

110

"""Built-in static body for attaching static shapes"""

111

112

space.shapes: KeysView[Shape]

113

"""View of all shapes in the space"""

114

115

space.bodies: KeysView[Body]

116

"""View of all bodies in the space"""

117

118

space.constraints: KeysView[Constraint]

119

"""View of all constraints in the space"""

120

```

121

122

## Object Management

123

124

### Adding Objects

125

126

```python { .api }

127

def add(self, *objs: Union[Body, Shape, Constraint]) -> None:

128

"""

129

Add one or many shapes, bodies or constraints to the space.

130

131

Objects can be added even from collision callbacks - they will be

132

added at the end of the current step.

133

134

Args:

135

*objs: Bodies, shapes, and/or constraints to add

136

137

Example:

138

space.add(body, shape, constraint)

139

space.add(body)

140

space.add(shape1, shape2, joint)

141

"""

142

```

143

144

### Removing Objects

145

146

```python { .api }

147

def remove(self, *objs: Union[Body, Shape, Constraint]) -> None:

148

"""

149

Remove one or many shapes, bodies or constraints from the space.

150

151

Objects can be removed from collision callbacks - removal happens

152

at the end of the current step or removal call.

153

154

Args:

155

*objs: Objects to remove

156

157

Note:

158

When removing bodies, also remove attached shapes and constraints

159

to avoid dangling references.

160

161

Example:

162

space.remove(body, shape)

163

space.remove(constraint)

164

"""

165

```

166

167

## Simulation Control

168

169

### Time Stepping

170

171

```python { .api }

172

def step(self, dt: float) -> None:

173

"""

174

Advance the simulation by the given time step.

175

176

Using fixed time steps is highly recommended for stability and

177

contact persistence efficiency.

178

179

Args:

180

dt: Time step in seconds

181

182

Example:

183

# 60 FPS with substeps for accuracy

184

steps = 5

185

for _ in range(steps):

186

space.step(1/60.0/steps)

187

"""

188

```

189

190

### Spatial Index Optimization

191

192

```python { .api }

193

def use_spatial_hash(self, dim: float, count: int) -> None:

194

"""

195

Switch from default bounding box tree to spatial hash indexing.

196

197

Spatial hash can be faster for large numbers (1000s) of same-sized objects

198

but requires tuning and is usually slower for varied object sizes.

199

200

Args:

201

dim: Hash cell size - should match average collision shape size

202

count: Minimum hash table size - try ~10x number of objects

203

204

Example:

205

# For 500 objects of ~50 pixel size

206

space.use_spatial_hash(50.0, 5000)

207

"""

208

209

def reindex_shape(self, shape: Shape) -> None:

210

"""Update spatial index for a single shape after manual position changes"""

211

212

def reindex_shapes_for_body(self, body: Body) -> None:

213

"""Update spatial index for all shapes on a body after position changes"""

214

215

def reindex_static(self) -> None:

216

"""Update spatial index for all static shapes after moving them"""

217

```

218

219

## Spatial Queries

220

221

### Point Queries

222

223

```python { .api }

224

def point_query(

225

self,

226

point: tuple[float, float],

227

max_distance: float,

228

shape_filter: ShapeFilter

229

) -> list[PointQueryInfo]:

230

"""

231

Query for shapes near a point within max_distance.

232

233

Args:

234

point: Query point (x, y)

235

max_distance: Maximum distance to search

236

- 0.0: point must be inside shapes

237

- negative: point must be certain depth inside shapes

238

shape_filter: Collision filter to apply

239

240

Returns:

241

List of PointQueryInfo with shape, point, distance, gradient

242

243

Example:

244

hits = space.point_query((100, 200), 50, pymunk.ShapeFilter())

245

for hit in hits:

246

print(f"Hit {hit.shape} at distance {hit.distance}")

247

"""

248

249

def point_query_nearest(

250

self,

251

point: tuple[float, float],

252

max_distance: float,

253

shape_filter: ShapeFilter

254

) -> Optional[PointQueryInfo]:

255

"""

256

Query for the single nearest shape to a point.

257

258

Returns None if no shapes found within max_distance.

259

Same parameters and behavior as point_query but returns only closest hit.

260

"""

261

```

262

263

### Line Segment Queries

264

265

```python { .api }

266

def segment_query(

267

self,

268

start: tuple[float, float],

269

end: tuple[float, float],

270

radius: float,

271

shape_filter: ShapeFilter

272

) -> list[SegmentQueryInfo]:

273

"""

274

Query for shapes intersecting a line segment with thickness.

275

276

Args:

277

start: Segment start point (x, y)

278

end: Segment end point (x, y)

279

radius: Segment thickness radius (0 for infinitely thin line)

280

shape_filter: Collision filter to apply

281

282

Returns:

283

List of SegmentQueryInfo with shape, point, normal, alpha

284

285

Example:

286

# Raycast from (0,0) to (100,100)

287

hits = space.segment_query((0, 0), (100, 100), 0, pymunk.ShapeFilter())

288

for hit in hits:

289

print(f"Hit {hit.shape} at {hit.point} with normal {hit.normal}")

290

"""

291

292

def segment_query_first(

293

self,

294

start: tuple[float, float],

295

end: tuple[float, float],

296

radius: float,

297

shape_filter: ShapeFilter

298

) -> Optional[SegmentQueryInfo]:

299

"""

300

Query for the first shape hit by a line segment.

301

302

Returns None if no shapes intersected.

303

Same parameters as segment_query but returns only the first/closest hit.

304

"""

305

```

306

307

### Area and Shape Queries

308

309

```python { .api }

310

def bb_query(self, bb: BB, shape_filter: ShapeFilter) -> list[Shape]:

311

"""

312

Query for shapes overlapping a bounding box.

313

314

Args:

315

bb: Bounding box to query

316

shape_filter: Collision filter to apply

317

318

Returns:

319

List of shapes overlapping the bounding box

320

321

Example:

322

bb = pymunk.BB(left=0, bottom=0, right=100, top=100)

323

shapes = space.bb_query(bb, pymunk.ShapeFilter())

324

"""

325

326

def shape_query(self, shape: Shape) -> list[ShapeQueryInfo]:

327

"""

328

Query for shapes overlapping the given shape.

329

330

Args:

331

shape: Shape to test overlaps with

332

333

Returns:

334

List of ShapeQueryInfo with overlapping shapes and contact info

335

336

Example:

337

test_shape = pymunk.Circle(None, 25, (100, 100))

338

overlaps = space.shape_query(test_shape)

339

for overlap in overlaps:

340

print(f"Overlapping with {overlap.shape}")

341

"""

342

```

343

344

## Collision Handling

345

346

### Collision Callbacks

347

348

```python { .api }

349

def on_collision(

350

self,

351

collision_type_a: Optional[int] = None,

352

collision_type_b: Optional[int] = None,

353

begin: Optional[Callable] = None,

354

pre_solve: Optional[Callable] = None,

355

post_solve: Optional[Callable] = None,

356

separate: Optional[Callable] = None,

357

data: Any = None

358

) -> None:

359

"""

360

Set callbacks for collision handling between specific collision types.

361

362

Args:

363

collision_type_a: First collision type (None matches any)

364

collision_type_b: Second collision type (None matches any)

365

begin: Called when shapes first touch

366

pre_solve: Called before collision resolution

367

post_solve: Called after collision resolution

368

separate: Called when shapes stop touching

369

data: User data passed to callbacks

370

371

Callback Signature:

372

def callback(arbiter: Arbiter, space: Space, data: Any) -> bool:

373

# Return True to process collision, False to ignore

374

return True

375

376

Example:

377

def player_enemy_collision(arbiter, space, data):

378

player_shape, enemy_shape = arbiter.shapes

379

print("Player hit enemy!")

380

return True # Process collision normally

381

382

space.on_collision(

383

collision_type_a=PLAYER_TYPE,

384

collision_type_b=ENEMY_TYPE,

385

begin=player_enemy_collision

386

)

387

"""

388

```

389

390

### Post-Step Callbacks

391

392

```python { .api }

393

def add_post_step_callback(

394

self,

395

func: Callable,

396

key: Hashable,

397

*args,

398

**kwargs

399

) -> None:

400

"""

401

Add a callback to be called after the current simulation step.

402

403

Useful for making changes that can't be done during collision callbacks,

404

like adding/removing objects or changing properties.

405

406

Args:

407

func: Function to call after step completes

408

key: Unique key for this callback (prevents duplicates)

409

*args, **kwargs: Arguments passed to func

410

411

Example:

412

def remove_object(space, obj):

413

space.remove(obj)

414

415

# Schedule removal after current step

416

space.add_post_step_callback(remove_object, "remove_ball", ball)

417

"""

418

```

419

420

## Debug Visualization

421

422

```python { .api }

423

def debug_draw(self, options: SpaceDebugDrawOptions) -> None:

424

"""

425

Draw debug visualization of the space using provided draw options.

426

427

Args:

428

options: Debug drawing implementation (pygame_util, pyglet_util, etc.)

429

430

Example:

431

import pygame

432

import pymunk.pygame_util

433

434

screen = pygame.display.set_mode((800, 600))

435

draw_options = pymunk.pygame_util.DrawOptions(screen)

436

437

# Customize colors

438

draw_options.shape_outline_color = (255, 0, 0, 255) # Red outlines

439

440

space.debug_draw(draw_options)

441

"""

442

```

443

444

## Usage Examples

445

446

### Basic Physics World

447

448

```python { .api }

449

import pymunk

450

451

# Create physics world

452

space = pymunk.Space()

453

space.gravity = (0, -982) # Earth gravity

454

space.iterations = 15 # Higher accuracy

455

456

# Create ground

457

ground = space.static_body

458

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

459

ground_shape.friction = 0.7

460

space.add(ground_shape)

461

462

# Create falling box

463

mass = 10

464

size = (50, 50)

465

moment = pymunk.moment_for_box(mass, size)

466

body = pymunk.Body(mass, moment)

467

body.position = 400, 300

468

469

shape = pymunk.Poly.create_box(body, size)

470

shape.friction = 0.7

471

shape.elasticity = 0.8

472

space.add(body, shape)

473

474

# Run simulation

475

dt = 1/60.0

476

for i in range(300): # 5 seconds

477

space.step(dt)

478

print(f"Position: {body.position}, Velocity: {body.velocity}")

479

```

480

481

### Collision Detection

482

483

```python { .api }

484

import pymunk

485

486

BALL_TYPE = 1

487

WALL_TYPE = 2

488

489

def ball_wall_collision(arbiter, space, data):

490

"""Handle ball hitting wall"""

491

impulse = arbiter.total_impulse

492

if abs(impulse) > 100: # Hard hit

493

print(f"Ball bounced hard! Impulse: {impulse}")

494

return True

495

496

space = pymunk.Space()

497

space.on_collision(BALL_TYPE, WALL_TYPE, begin=ball_wall_collision)

498

499

# Create ball with collision type

500

ball_body = pymunk.Body(1, pymunk.moment_for_circle(1, 0, 20))

501

ball_shape = pymunk.Circle(ball_body, 20)

502

ball_shape.collision_type = BALL_TYPE

503

ball_shape.elasticity = 0.9

504

505

# Create wall with collision type

506

wall_body = space.static_body

507

wall_shape = pymunk.Segment(wall_body, (0, 0), (800, 0), 5)

508

wall_shape.collision_type = WALL_TYPE

509

510

space.add(ball_body, ball_shape, wall_shape)

511

```

512

513

### Advanced Spatial Queries

514

515

```python { .api }

516

import pymunk

517

518

space = pymunk.Space()

519

# ... add objects to space ...

520

521

# Point query - find what's under mouse cursor

522

mouse_pos = (400, 300)

523

shapes_under_mouse = space.point_query(

524

mouse_pos, 0, pymunk.ShapeFilter()

525

)

526

527

if shapes_under_mouse:

528

print(f"Mouse over: {shapes_under_mouse[0].shape}")

529

530

# Raycast - line of sight check

531

start = (100, 100)

532

end = (700, 500)

533

line_of_sight = space.segment_query_first(

534

start, end, 0, pymunk.ShapeFilter()

535

)

536

537

if line_of_sight:

538

print(f"LOS blocked at {line_of_sight.point}")

539

else:

540

print("Clear line of sight")

541

542

# Area query - explosion radius

543

explosion_center = (400, 300)

544

explosion_radius = 100

545

explosion_bb = pymunk.BB.newForCircle(explosion_center, explosion_radius)

546

affected_shapes = space.bb_query(explosion_bb, pymunk.ShapeFilter())

547

548

for shape in affected_shapes:

549

# Apply explosion force

550

if shape.body.body_type == pymunk.Body.DYNAMIC:

551

direction = shape.body.position - explosion_center

552

force = direction.normalized() * 10000

553

shape.body.apply_impulse_at_world_point(force, explosion_center)

554

```

555

556

### Performance Optimization

557

558

```python { .api }

559

import pymunk

560

561

space = pymunk.Space(threaded=True) # Enable threading

562

space.threads = 2 # Use 2 threads

563

564

# For large numbers of similar-sized objects

565

if num_objects > 1000:

566

avg_object_size = 25 # Average collision shape size

567

space.use_spatial_hash(avg_object_size, num_objects * 10)

568

569

# Enable sleeping for better performance

570

space.sleep_time_threshold = 0.5 # Sleep after 0.5 seconds idle

571

572

# Optimize solver iterations vs accuracy

573

space.iterations = 8 # Reduce for better performance

574

575

# Use appropriate gravity and damping

576

space.gravity = (0, -981) # Realistic gravity

577

space.damping = 0.999 # Small amount of air resistance

578

```

579

580

The Space class provides complete control over physics simulation with efficient spatial queries, flexible collision handling, and performance optimization options suitable for games, simulations, and interactive applications.