0
# Debug Visualization and Drawing
1
2
Pymunk provides comprehensive debug visualization capabilities for popular graphics libraries, enabling easy debugging and prototyping of physics simulations with visual feedback.
3
4
## SpaceDebugDrawOptions - Base Class
5
6
The foundation for all debug drawing implementations, providing customizable rendering options and colors.
7
8
### Class Definition
9
10
```python { .api }
11
class SpaceDebugDrawOptions:
12
"""
13
Base class for configuring debug drawing of physics spaces.
14
15
Provides customizable colors, drawing flags, and transformation options.
16
Subclassed by library-specific implementations (pygame, pyglet, matplotlib).
17
"""
18
19
# Drawing flags (combine with bitwise OR)
20
DRAW_SHAPES: int # Draw collision shapes
21
DRAW_CONSTRAINTS: int # Draw joints/constraints
22
DRAW_COLLISION_POINTS: int # Draw collision contact points
23
24
def __init__(self) -> None:
25
"""
26
Create debug draw options with default settings.
27
28
Default flags enable drawing of shapes, constraints, and collision points.
29
"""
30
```
31
32
### Drawing Control Flags
33
34
```python { .api }
35
# Control what elements are drawn
36
options.flags: int = (
37
SpaceDebugDrawOptions.DRAW_SHAPES |
38
SpaceDebugDrawOptions.DRAW_CONSTRAINTS |
39
SpaceDebugDrawOptions.DRAW_COLLISION_POINTS
40
)
41
"""
42
Bitmask controlling what elements to draw.
43
Combine flags with bitwise OR to enable multiple elements.
44
45
Examples:
46
options.flags = SpaceDebugDrawOptions.DRAW_SHAPES # Only shapes
47
options.flags = SpaceDebugDrawOptions.DRAW_SHAPES | SpaceDebugDrawOptions.DRAW_CONSTRAINTS # Shapes + constraints
48
options.flags = 0 # Draw nothing
49
"""
50
```
51
52
### Color Configuration
53
54
```python { .api }
55
# Shape colors based on body type
56
options.shape_dynamic_color: SpaceDebugColor = SpaceDebugColor(52, 152, 219, 255)
57
"""Color for dynamic body shapes (blue)"""
58
59
options.shape_static_color: SpaceDebugColor = SpaceDebugColor(149, 165, 166, 255)
60
"""Color for static body shapes (gray)"""
61
62
options.shape_kinematic_color: SpaceDebugColor = SpaceDebugColor(39, 174, 96, 255)
63
"""Color for kinematic body shapes (green)"""
64
65
options.shape_sleeping_color: SpaceDebugColor = SpaceDebugColor(114, 148, 168, 255)
66
"""Color for sleeping body shapes (darker blue)"""
67
68
# Element-specific colors
69
options.shape_outline_color: SpaceDebugColor = SpaceDebugColor(44, 62, 80, 255)
70
"""Color for shape outlines (dark blue-gray)"""
71
72
options.constraint_color: SpaceDebugColor = SpaceDebugColor(142, 68, 173, 255)
73
"""Color for constraints/joints (purple)"""
74
75
options.collision_point_color: SpaceDebugColor = SpaceDebugColor(231, 76, 60, 255)
76
"""Color for collision points (red)"""
77
```
78
79
### Coordinate Transformation
80
81
```python { .api }
82
options.transform: Transform = Transform.identity()
83
"""
84
Transformation applied to all drawing coordinates.
85
86
Use to implement camera/viewport transformations:
87
- Translation: Move view to follow objects
88
- Scaling: Zoom in/out
89
- Rotation: Rotate view
90
91
Example:
92
# Camera following player
93
camera_pos = player.position
94
options.transform = Transform.translation(-camera_pos.x, -camera_pos.y)
95
96
# Zoom and center
97
zoom = 2.0
98
center = (400, 300) # Screen center
99
options.transform = (
100
Transform.translation(*center) @
101
Transform.scaling(zoom) @
102
Transform.translation(-camera_pos.x, -camera_pos.y)
103
)
104
"""
105
```
106
107
## SpaceDebugColor - Color Utility
108
109
```python { .api }
110
class SpaceDebugColor(NamedTuple):
111
"""
112
RGBA color tuple for debug drawing.
113
114
Values are typically 0-255 for integer colors or 0.0-1.0 for float colors.
115
"""
116
117
r: float # Red component
118
g: float # Green component
119
b: float # Blue component
120
a: float # Alpha component (opacity)
121
122
def as_int(self) -> tuple[int, int, int, int]:
123
"""
124
Return color as integer tuple (0-255 range).
125
126
Example:
127
color = SpaceDebugColor(1.0, 0.5, 0.0, 1.0)
128
rgba_int = color.as_int() # (255, 128, 0, 255)
129
"""
130
131
def as_float(self) -> tuple[float, float, float, float]:
132
"""
133
Return color as float tuple (0.0-1.0 range).
134
135
Example:
136
color = SpaceDebugColor(255, 128, 0, 255)
137
rgba_float = color.as_float() # (1.0, 0.5, 0.0, 1.0)
138
"""
139
140
# Common colors
141
RED = SpaceDebugColor(255, 0, 0, 255)
142
GREEN = SpaceDebugColor(0, 255, 0, 255)
143
BLUE = SpaceDebugColor(0, 0, 255, 255)
144
WHITE = SpaceDebugColor(255, 255, 255, 255)
145
BLACK = SpaceDebugColor(0, 0, 0, 255)
146
TRANSPARENT = SpaceDebugColor(0, 0, 0, 0)
147
```
148
149
## Pygame Integration
150
151
High-performance debug drawing for Pygame applications with coordinate system handling.
152
153
### Setup and Usage
154
155
```python { .api }
156
import pygame
157
import pymunk
158
import pymunk.pygame_util
159
160
# Initialize Pygame
161
pygame.init()
162
screen = pygame.display.set_mode((800, 600))
163
clock = pygame.time.Clock()
164
165
# Create physics space
166
space = pymunk.Space()
167
space.gravity = (0, -981)
168
169
# Create debug drawing options
170
draw_options = pymunk.pygame_util.DrawOptions(screen)
171
172
# Main loop
173
running = True
174
while running:
175
for event in pygame.event.get():
176
if event.type == pygame.QUIT:
177
running = False
178
179
# Clear screen
180
screen.fill((255, 255, 255)) # White background
181
182
# Step physics
183
dt = clock.tick(60) / 1000.0
184
space.step(dt)
185
186
# Draw physics debug visualization
187
space.debug_draw(draw_options)
188
189
pygame.display.flip()
190
191
pygame.quit()
192
```
193
194
### Coordinate System Configuration
195
196
```python { .api }
197
import pymunk.pygame_util
198
199
# Pygame uses Y-down coordinates, physics often uses Y-up
200
# Configure coordinate system behavior:
201
202
pymunk.pygame_util.positive_y_is_up = False # Default - Y increases downward
203
"""
204
Control Y-axis direction:
205
- False (default): Y increases downward (standard Pygame)
206
- True: Y increases upward (mathematical convention)
207
208
When True: When False:
209
y ^ +------ > x
210
| |
211
+---> x v y
212
"""
213
214
# If using Y-up physics, set gravity appropriately:
215
if pymunk.pygame_util.positive_y_is_up:
216
space.gravity = (0, -981) # Downward gravity
217
else:
218
space.gravity = (0, 981) # "Upward" gravity (actually downward in screen coords)
219
```
220
221
### Coordinate Conversion Utilities
222
223
```python { .api }
224
def get_mouse_pos(surface: pygame.Surface) -> Vec2d:
225
"""
226
Get mouse position converted to physics coordinates.
227
228
Handles coordinate system conversion based on positive_y_is_up setting.
229
230
Example:
231
mouse_pos = pymunk.pygame_util.get_mouse_pos(screen)
232
hit = space.point_query_nearest(mouse_pos, 0, pymunk.ShapeFilter())
233
"""
234
235
def to_pygame(point: Vec2d, surface: pygame.Surface) -> tuple[int, int]:
236
"""
237
Convert physics coordinates to Pygame screen coordinates.
238
239
Args:
240
point: Physics world coordinate
241
surface: Pygame surface for height reference
242
243
Returns:
244
(x, y) tuple in Pygame coordinates
245
"""
246
247
def from_pygame(point: tuple[int, int], surface: pygame.Surface) -> Vec2d:
248
"""
249
Convert Pygame screen coordinates to physics coordinates.
250
251
Args:
252
point: Pygame coordinate (x, y)
253
surface: Pygame surface for height reference
254
255
Returns:
256
Vec2d in physics world coordinates
257
"""
258
```
259
260
### Custom Colors
261
262
```python { .api }
263
import pygame
264
import pymunk.pygame_util
265
266
# Create debug options
267
draw_options = pymunk.pygame_util.DrawOptions(screen)
268
269
# Customize colors
270
draw_options.shape_outline_color = pymunk.SpaceDebugColor(255, 0, 0, 255) # Red outlines
271
draw_options.constraint_color = pymunk.SpaceDebugColor(0, 255, 0, 255) # Green constraints
272
draw_options.collision_point_color = pymunk.SpaceDebugColor(255, 255, 0, 255) # Yellow collision points
273
274
# Set individual shape colors
275
circle_shape.color = pygame.Color("pink")
276
box_shape.color = (0, 255, 255, 255) # Cyan
277
278
# Control what gets drawn
279
draw_options.flags = pymunk.SpaceDebugDrawOptions.DRAW_SHAPES # Only shapes, no constraints
280
```
281
282
## Pyglet Integration
283
284
OpenGL-based debug drawing with batched rendering for performance.
285
286
### Setup and Usage
287
288
```python { .api }
289
import pyglet
290
import pymunk
291
import pymunk.pyglet_util
292
293
# Create window
294
window = pyglet.window.Window(800, 600, "Physics Debug")
295
296
# Create physics space
297
space = pymunk.Space()
298
space.gravity = (0, -981)
299
300
# Create debug drawing options
301
draw_options = pymunk.pyglet_util.DrawOptions()
302
303
@window.event
304
def on_draw():
305
window.clear()
306
307
# Draw physics debug visualization
308
space.debug_draw(draw_options)
309
310
def update(dt):
311
space.step(dt)
312
313
# Schedule physics updates
314
pyglet.clock.schedule_interval(update, 1/60.0)
315
316
# Run application
317
pyglet.app.run()
318
```
319
320
### Batched Drawing for Performance
321
322
```python { .api }
323
import pyglet
324
import pymunk.pyglet_util
325
326
# Create custom batch for optimized rendering
327
batch = pyglet.graphics.Batch()
328
329
# Create debug options with custom batch
330
draw_options = pymunk.pyglet_util.DrawOptions(batch=batch)
331
332
@window.event
333
def on_draw():
334
window.clear()
335
336
# Draw physics (adds to batch, doesn't render immediately)
337
space.debug_draw(draw_options)
338
339
# Render entire batch efficiently
340
batch.draw()
341
```
342
343
### Context Manager Support
344
345
```python { .api }
346
import pyglet
347
import pymunk.pyglet_util
348
349
draw_options = pymunk.pyglet_util.DrawOptions()
350
351
@window.event
352
def on_draw():
353
window.clear()
354
355
# Use context manager for automatic OpenGL state management
356
with draw_options:
357
space.debug_draw(draw_options)
358
```
359
360
### Custom Shape Colors
361
362
```python { .api }
363
import pyglet
364
import pymunk.pyglet_util
365
366
# Set shape-specific colors
367
circle_shape.color = (255, 0, 0, 255) # Red circle
368
box_shape.color = (0, 255, 0, 255) # Green box
369
segment_shape.color = (0, 0, 255, 255) # Blue segment
370
371
# Use Pyglet color utilities
372
import pyglet.image
373
circle_shape.color = pyglet.image.get_color("pink")
374
```
375
376
## Matplotlib Integration
377
378
Publication-quality debug visualization for analysis and documentation.
379
380
### Setup and Usage
381
382
```python { .api }
383
import matplotlib.pyplot as plt
384
import pymunk
385
import pymunk.matplotlib_util
386
387
# Create figure and axes
388
fig, ax = plt.subplots(figsize=(10, 8))
389
390
# Create physics space
391
space = pymunk.Space()
392
space.gravity = (0, -981)
393
394
# Add some physics objects...
395
# ... physics simulation code ...
396
397
# Create debug drawing options
398
draw_options = pymunk.matplotlib_util.DrawOptions(ax)
399
400
# Draw physics state
401
space.debug_draw(draw_options)
402
403
# Configure plot
404
ax.set_xlim(0, 800)
405
ax.set_ylim(0, 600)
406
ax.set_aspect('equal')
407
ax.grid(True, alpha=0.3)
408
ax.set_title('Physics Simulation Debug View')
409
410
plt.show()
411
```
412
413
### Animation and Time Series
414
415
```python { .api }
416
import matplotlib.pyplot as plt
417
import matplotlib.animation as animation
418
import pymunk
419
import pymunk.matplotlib_util
420
421
fig, ax = plt.subplots()
422
space = pymunk.Space()
423
draw_options = pymunk.matplotlib_util.DrawOptions(ax)
424
425
def animate(frame):
426
ax.clear()
427
428
# Step physics
429
space.step(1/60.0)
430
431
# Draw current state
432
space.debug_draw(draw_options)
433
434
# Configure axes
435
ax.set_xlim(0, 800)
436
ax.set_ylim(0, 600)
437
ax.set_aspect('equal')
438
ax.set_title(f'Frame {frame}')
439
440
# Create animation
441
anim = animation.FuncAnimation(fig, animate, interval=16, blit=False)
442
plt.show()
443
444
# Save as video
445
# anim.save('physics_simulation.mp4', writer='ffmpeg', fps=60)
446
```
447
448
### Custom Shape Styling
449
450
```python { .api }
451
import matplotlib.pyplot as plt
452
import pymunk.matplotlib_util
453
454
# Set shape colors using matplotlib color formats
455
circle_shape.color = (1.0, 0.0, 0.0, 1.0) # Red (RGBA floats)
456
box_shape.color = 'blue' # Named color
457
segment_shape.color = '#00FF00' # Hex color
458
459
# Create debug options with custom styling
460
draw_options = pymunk.matplotlib_util.DrawOptions(ax)
461
draw_options.shape_outline_color = pymunk.SpaceDebugColor(0, 0, 0, 255) # Black outlines
462
```
463
464
## Advanced Visualization Techniques
465
466
### Camera and Viewport Control
467
468
```python { .api }
469
import pymunk
470
471
class Camera:
472
def __init__(self, position=(0, 0), zoom=1.0):
473
self.position = pymunk.Vec2d(*position)
474
self.zoom = zoom
475
476
def follow_body(self, body, screen_size):
477
"""Make camera follow a body"""
478
self.position = body.position
479
480
def get_transform(self, screen_size):
481
"""Get transform matrix for debug drawing"""
482
screen_center = pymunk.Vec2d(*screen_size) * 0.5
483
484
return (
485
pymunk.Transform.translation(*screen_center) @
486
pymunk.Transform.scaling(self.zoom) @
487
pymunk.Transform.translation(-self.position.x, -self.position.y)
488
)
489
490
# Usage with any debug drawing system
491
camera = Camera()
492
camera.follow_body(player_body, (800, 600))
493
494
draw_options.transform = camera.get_transform((800, 600))
495
space.debug_draw(draw_options)
496
```
497
498
### Selective Drawing
499
500
```python { .api }
501
import pymunk
502
503
class SelectiveDrawOptions(pymunk.pygame_util.DrawOptions):
504
"""Custom draw options that only draw specific shapes"""
505
506
def __init__(self, surface, shape_filter=None):
507
super().__init__(surface)
508
self.shape_filter = shape_filter or (lambda shape: True)
509
510
def draw_shape(self, shape):
511
"""Override to implement selective drawing"""
512
if self.shape_filter(shape):
513
super().draw_shape(shape)
514
515
# Filter functions
516
def draw_only_dynamic(shape):
517
return shape.body.body_type == pymunk.Body.DYNAMIC
518
519
def draw_only_circles(shape):
520
return isinstance(shape, pymunk.Circle)
521
522
# Usage
523
draw_options = SelectiveDrawOptions(screen, draw_only_dynamic)
524
space.debug_draw(draw_options)
525
```
526
527
### Performance Monitoring
528
529
```python { .api }
530
import time
531
import pymunk
532
533
class ProfiledDrawOptions(pymunk.pygame_util.DrawOptions):
534
"""Draw options with performance monitoring"""
535
536
def __init__(self, surface):
537
super().__init__(surface)
538
self.draw_time = 0
539
self.shape_count = 0
540
541
def draw_shape(self, shape):
542
start_time = time.perf_counter()
543
super().draw_shape(shape)
544
self.draw_time += time.perf_counter() - start_time
545
self.shape_count += 1
546
547
def reset_stats(self):
548
self.draw_time = 0
549
self.shape_count = 0
550
551
# Usage
552
draw_options = ProfiledDrawOptions(screen)
553
space.debug_draw(draw_options)
554
555
print(f"Drew {draw_options.shape_count} shapes in {draw_options.draw_time:.3f}s")
556
draw_options.reset_stats()
557
```
558
559
## Integration Examples
560
561
### Complete Pygame Example
562
563
```python { .api }
564
import pygame
565
import pymunk
566
import pymunk.pygame_util
567
import math
568
569
# Initialize Pygame
570
pygame.init()
571
screen = pygame.display.set_mode((800, 600))
572
pygame.display.set_caption("Physics Debug Visualization")
573
clock = pygame.time.Clock()
574
575
# Create physics world
576
space = pymunk.Space()
577
space.gravity = (0, 981) # Downward gravity for Y-down coordinate system
578
579
# Create ground
580
ground_body = space.static_body
581
ground = pymunk.Segment(ground_body, (0, 550), (800, 550), 5)
582
ground.friction = 0.7
583
space.add(ground)
584
585
# Create falling objects
586
def create_ball(x, y):
587
mass = 10
588
radius = 20
589
moment = pymunk.moment_for_circle(mass, 0, radius)
590
body = pymunk.Body(mass, moment)
591
body.position = x, y
592
shape = pymunk.Circle(body, radius)
593
shape.friction = 0.7
594
shape.elasticity = 0.8
595
shape.color = pygame.Color("red") # Custom color
596
space.add(body, shape)
597
598
def create_box(x, y):
599
mass = 15
600
size = (40, 40)
601
moment = pymunk.moment_for_box(mass, size)
602
body = pymunk.Body(mass, moment)
603
body.position = x, y
604
shape = pymunk.Poly.create_box(body, size)
605
shape.friction = 0.7
606
shape.elasticity = 0.3
607
shape.color = pygame.Color("blue")
608
space.add(body, shape)
609
610
# Create debug drawing options
611
draw_options = pymunk.pygame_util.DrawOptions(screen)
612
draw_options.flags = (
613
pymunk.SpaceDebugDrawOptions.DRAW_SHAPES |
614
pymunk.SpaceDebugDrawOptions.DRAW_CONSTRAINTS
615
) # Don't draw collision points for cleaner view
616
617
# Main game loop
618
running = True
619
while running:
620
dt = clock.tick(60) / 1000.0
621
622
for event in pygame.event.get():
623
if event.type == pygame.QUIT:
624
running = False
625
elif event.type == pygame.MOUSEBUTTONDOWN:
626
mouse_pos = pygame.mouse.get_pos()
627
if event.button == 1: # Left click - ball
628
create_ball(*mouse_pos)
629
elif event.button == 3: # Right click - box
630
create_box(*mouse_pos)
631
632
# Step physics
633
space.step(dt)
634
635
# Clear screen
636
screen.fill((255, 255, 255))
637
638
# Draw physics debug visualization
639
space.debug_draw(draw_options)
640
641
# Draw UI
642
font = pygame.font.Font(None, 36)
643
text = font.render("Left click: Ball, Right click: Box", True, (0, 0, 0))
644
screen.blit(text, (10, 10))
645
646
pygame.display.flip()
647
648
pygame.quit()
649
```
650
651
### Multi-View Debugging
652
653
```python { .api }
654
import pygame
655
import pymunk
656
import pymunk.pygame_util
657
658
# Create multiple views for different debug information
659
screen = pygame.display.set_mode((1200, 600))
660
661
# Create subsurfaces for different views
662
main_view = screen.subsurface((0, 0, 800, 600))
663
detail_view = screen.subsurface((800, 0, 400, 300))
664
stats_view = screen.subsurface((800, 300, 400, 300))
665
666
# Create different draw options for each view
667
main_draw = pymunk.pygame_util.DrawOptions(main_view)
668
main_draw.flags = pymunk.SpaceDebugDrawOptions.DRAW_SHAPES
669
670
detail_draw = pymunk.pygame_util.DrawOptions(detail_view)
671
detail_draw.flags = (
672
pymunk.SpaceDebugDrawOptions.DRAW_SHAPES |
673
pymunk.SpaceDebugDrawOptions.DRAW_CONSTRAINTS |
674
pymunk.SpaceDebugDrawOptions.DRAW_COLLISION_POINTS
675
)
676
677
# Different zoom levels
678
main_draw.transform = pymunk.Transform.identity()
679
detail_draw.transform = pymunk.Transform.scaling(2.0) # 2x zoom
680
681
# In main loop:
682
main_view.fill((255, 255, 255))
683
detail_view.fill((240, 240, 240))
684
685
space.debug_draw(main_draw)
686
space.debug_draw(detail_draw)
687
688
# Draw stats on stats_view
689
# ... statistics rendering code ...
690
```
691
692
Pymunk's visualization utilities provide powerful debugging capabilities across multiple graphics libraries, enabling rapid prototyping, debugging, and analysis of physics simulations with minimal setup and maximum flexibility.