1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# C++ version Copyright (c) 2006-2007 Erin Catto http://www.box2d.org
5# Python version Copyright (c) 2010 kne / sirkne at gmail dot com
6#
7# This software is provided 'as-is', without any express or implied
8# warranty.  In no event will the authors be held liable for any damages
9# arising from the use of this software.
10# Permission is granted to anyone to use this software for any purpose,
11# including commercial applications, and to alter it and redistribute it
12# freely, subject to the following restrictions:
13# 1. The origin of this software must not be misrepresented; you must not
14# claim that you wrote the original software. If you use this software
15# in a product, an acknowledgment in the product documentation would be
16# appreciated but is not required.
17# 2. Altered source versions must be plainly marked as such, and must not be
18# misrepresented as being the original software.
19# 3. This notice may not be removed or altered from any source distribution.
20
21"""
22A simple, minimal Pygame-based backend.
23It will only draw and support very basic keyboard input (ESC to quit).
24
25There are no main dependencies other than the actual test you are running.
26Note that this only relies on framework.py for the loading of this backend,
27and holding the Keys class. If you write a test that depends only on this
28backend, you can remove references to that file here and import this module
29directly in your test.
30
31To use this backend, try:
32 % python -m examples.web --backend simple
33
34NOTE: Examples with Step() re-implemented are not yet supported, as I wanted
35to do away with the Settings class. This means the following will definitely
36not work: Breakable, Liquid, Raycast, TimeOfImpact, ... (incomplete)
37"""
38
39import pygame
40from pygame.locals import (QUIT, KEYDOWN, KEYUP)
41import Box2D
42from Box2D.b2 import (world, staticBody, dynamicBody, kinematicBody,
43                      polygonShape, circleShape, edgeShape, loopShape)
44
45TARGET_FPS = 60
46PPM = 10.0
47TIMESTEP = 1.0 / TARGET_FPS
48VEL_ITERS, POS_ITERS = 10, 10
49SCREEN_WIDTH, SCREEN_HEIGHT = 800, 600
50SCREEN_OFFSETX, SCREEN_OFFSETY = SCREEN_WIDTH * 1.0 / 2.0, SCREEN_HEIGHT * 2.0 / 3
51colors = {
52    staticBody: (255, 255, 255, 255),
53    dynamicBody: (127, 127, 127, 255),
54    kinematicBody: (127, 127, 230, 255),
55}
56
57
58def fix_vertices(vertices):
59    return [(int(SCREEN_OFFSETX + v[0]), int(SCREEN_OFFSETY - v[1])) for v in vertices]
60
61
62def _draw_polygon(polygon, screen, body, fixture):
63    transform = body.transform
64    vertices = fix_vertices([transform * v * PPM for v in polygon.vertices])
65    pygame.draw.polygon(
66        screen, [c / 2.0 for c in colors[body.type]], vertices, 0)
67    pygame.draw.polygon(screen, colors[body.type], vertices, 1)
68polygonShape.draw = _draw_polygon
69
70
71def _draw_circle(circle, screen, body, fixture):
72    position = fix_vertices([body.transform * circle.pos * PPM])[0]
73    pygame.draw.circle(screen, colors[body.type],
74                       position, int(circle.radius * PPM))
75circleShape.draw = _draw_circle
76
77
78def _draw_edge(edge, screen, body, fixture):
79    vertices = fix_vertices(
80        [body.transform * edge.vertex1 * PPM, body.transform * edge.vertex2 * PPM])
81    pygame.draw.line(screen, colors[body.type], vertices[0], vertices[1])
82edgeShape.draw = _draw_edge
83
84
85def _draw_loop(loop, screen, body, fixture):
86    transform = body.transform
87    vertices = fix_vertices([transform * v * PPM for v in loop.vertices])
88    v1 = vertices[-1]
89    for v2 in vertices:
90        pygame.draw.line(screen, colors[body.type], v1, v2)
91        v1 = v2
92loopShape.draw = _draw_loop
93
94
95def draw_world(screen, world):
96    # Draw the world
97    for body in world.bodies:
98        for fixture in body.fixtures:
99            fixture.shape.draw(screen, body, fixture)
100
101
102class Keys(object):
103    pass
104
105# The following import is only needed to do the initial loading and
106# overwrite the Keys class.
107import framework
108# Set up the keys (needed as the normal framework abstracts them between
109# backends)
110keys = [s for s in dir(pygame.locals) if s.startswith('K_')]
111for key in keys:
112    value = getattr(pygame.locals, key)
113    setattr(Keys, key, value)
114framework.Keys = Keys
115
116
117class SimpleFramework(object):
118    name = 'None'
119    description = ''
120
121    def __init__(self):
122        self.world = world()
123
124        print('Initializing pygame framework...')
125        # Pygame Initialization
126        pygame.init()
127        caption = "Python Box2D Testbed - Simple backend - " + self.name
128        pygame.display.set_caption(caption)
129
130        # Screen and debug draw
131        self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
132        self.font = pygame.font.Font(None, 15)
133
134        self.groundbody = self.world.CreateBody()
135
136    def run(self):
137        """
138        Main loop.
139
140        Updates the world and then the screen.
141        """
142
143        running = True
144        clock = pygame.time.Clock()
145        while running:
146            for event in pygame.event.get():
147                if event.type == QUIT or (event.type == KEYDOWN and event.key == Keys.K_ESCAPE):
148                    running = False
149                elif event.type == KEYDOWN:
150                    self.Keyboard(event.key)
151                elif event.type == KEYUP:
152                    self.KeyboardUp(event.key)
153
154            self.screen.fill((0, 0, 0))
155
156            self.textLine = 15
157
158            # Step the world
159            self.world.Step(TIMESTEP, VEL_ITERS, POS_ITERS)
160            self.world.ClearForces()
161
162            draw_world(self.screen, self.world)
163
164            # Draw the name of the test running
165            self.Print(self.name, (127, 127, 255))
166
167            if self.description:
168                # Draw the name of the test running
169                for s in self.description.split('\n'):
170                    self.Print(s, (127, 255, 127))
171
172            pygame.display.flip()
173            clock.tick(TARGET_FPS)
174            self.fps = clock.get_fps()
175
176        self.world.contactListener = None
177        self.world.destructionListener = None
178        self.world.renderer = None
179
180    def Print(self, str, color=(229, 153, 153, 255)):
181        """
182        Draw some text at the top status lines
183        and advance to the next line.
184        """
185        self.screen.blit(self.font.render(
186            str, True, color), (5, self.textLine))
187        self.textLine += 15
188
189    def Keyboard(self, key):
190        """
191        Callback indicating 'key' has been pressed down.
192        The keys are mapped after pygame's style.
193
194         from .framework import Keys
195         if key == Keys.K_z:
196             ...
197        """
198        pass
199
200    def KeyboardUp(self, key):
201        """
202        Callback indicating 'key' has been released.
203        See Keyboard() for key information
204        """
205        pass
206