|
SlideAndPinJointsExample
A step by step tutorial for the demo_slide_and_pinjoint example.
Slide and Pin Joint Demo Step by StepThis is a step by step tutorial explaining the demo demo_slide_and_pinjoint.py included in pymunk. It is probably a good idea to have the file near by if I miss something in the tutorial or something is unclear. 1 - Before we startFor this tutorial you will need:
In order to run pymunk you will need to have a compiled chipmunk library somewhere where pymunk can find it. If you are on windows, the easiest thing is to just unzip your downloaded pymunk package to a directory where you also place the chipmunk dll. (Or better, get pymunk directly from svn) If you are on a platform with no precompiled library of chipmunk such as Linux or OSX, you will have to compile it yourself. Chipmunk (and instuctions on how to build it) can be found at http://wiki.slembcke.net/main/published/Chipmunk 2 - An empty simulationOk, lets start. Chipmunk (and therefore pymunk) as a couple of central concepts, which is explained pretty good in this citation from the Chipmunk docs:
The documentation for chipmunk can be found here: http://files.slembcke.net/chipmunk/chipmunk-docs.html It is a good idea to read it, so do it now :) We are now ready to write some code: import sys
import pygame
from pygame.locals import *
from pygame.color import *
import pymunk as pm #1
def main():
pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption("Joints. Just wait and the L will tip over")
clock = pygame.time.Clock()
running = True
pm.init_pymunk() #2
space = pm.Space() #3
space.gravity = (0.0, -900.0)
while running:
for event in pygame.event.get():
if event.type == QUIT:
running = False
elif event.type == KEYDOWN and event.key == K_ESCAPE:
running = False
screen.fill(THECOLORS["white"])
space.step(1/50.0) #4
pygame.display.flip()
clock.tick(50)
if __name__ == '__main__':
sys.exit(main())The code will display a blank window, and will run a physics simulation of an empty space.
3 - Falling ballsThe easiest shape to handle (and draw) is the circle. Therefore our next step is to add make a ball spawn once in while. In most demos all code is in one big pile in the main() function as they are so small and easy, but I will extract some methods in this tutorial to make it more easy to follow. First, a function to add a ball to a space: def add_ball(space):
mass = 1
radius = 14
inertia = pm.moment_for_circle(mass, 0, radius, (0,0)) # 1
body = pm.Body(mass, inertia) # 2
x = random.randint(120,380)
body.position = x, 550 # 3
shape = pm.Circle(body, radius, (0,0)) # 4
space.add(body, shape) # 5
return shape
Now that we can create balls we want to display them: def draw_ball(screen, ball):
p = int(ball.body.position.x), 600-int(ball.body.position.y)
pygame.draw.circle(screen, THECOLORS["blue"], p, int(ball.radius), 2)As I have used pygame in this example, we can use the draw.circle function to draw the balls. But first we must convert the position of the ball. We earlier set the gravity to -900 (that is, it will point down the y axis). Pygame thinks 0,0 is at the top left of the screen, with y increasing downwards. So we make a simlpe conversion of the y value. With these two functions and a little code to spawn balls you should see a couple of balls falling. Yay! import sys, random
import pygame
from pygame.locals import *
from pygame.color import *
import pymunk as pm
#def add_ball(space):
#def draw_ball(screen, ball):
def main():
pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption("Joints. Just wait and the L will tip over")
clock = pygame.time.Clock()
running = True
pm.init_pymunk()
space = pm.Space()
space.gravity = (0.0, -900.0)
balls = []
ticks_to_next_ball = 10
while running:
for event in pygame.event.get():
if event.type == QUIT:
running = False
elif event.type == KEYDOWN and event.key == K_ESCAPE:
running = False
ticks_to_next_ball -= 1
if ticks_to_next_ball <= 0:
ticks_to_next_ball = 25
ball_shape = add_ball(space)
balls.append(ball_shape)
screen.fill(THECOLORS["white"])
for ball in balls:
draw_ball(screen, ball)
space.step(1/50.0)
pygame.display.flip()
clock.tick(50)
if __name__ == '__main__':
sys.exit(main()) 4 - A static LFalling balls are quite boring. We don't see any physics simulation except basic gravity, and everyone can do gravity without help from a physics library. So lets add something the balls can land on, two static lines forming an L. As with the balls we start with a function to add an L to the space: def add_static_L(space):
body = pm.Body(pm.inf, pm.inf) # 1
body.position = (300,300)
l1 = pm.Segment(body, (-150, 0), (255.0, 0.0), 5.0) # 2
l2 = pm.Segment(body, (-150.0, 0), (-150.0, 50.0), 5.0)
space.add(l1, l2) # 3
return l1,l2
Next we add a function to draw the L shape: def draw_lines(screen, lines):
for line in lines:
body = line.body
pv1 = body.position + line.a.rotated(math.degrees(body.angle)) # 1
pv2 = body.position + line.b.rotated(math.degrees(body.angle))
p1 = to_pygame(pv1) # 2
p2 = to_pygame(pv2)
pygame.draw.lines(screen, THECOLORS["lightgray"], False, [p1,p2])
def to_pygame(p):
"""Small hack to convert pymunk to pygame coordinates"""
return int(p.x), int(-p.y+600)We add a call to add_static_L() and one to draw_lines() and now we should see an inverted L shape in the middle will balls spawning and hitting the shape. import sys, random
import pygame
from pygame.locals import *
from pygame.color import *
import pymunk as pm
import math
#def to_pygame(p):
#def add_ball(space):
#def draw_ball(screen, ball):
#def add_static_l(space):
#def draw_lines(screen, lines):
def main():
pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption("Joints. Just wait and the L will tip over")
clock = pygame.time.Clock()
running = True
pm.init_pymunk()
space = pm.Space()
space.gravity = (0.0, -900.0)
lines = add_static_L(space)
balls = []
ticks_to_next_ball = 10
while running:
for event in pygame.event.get():
if event.type == QUIT:
running = False
elif event.type == KEYDOWN and event.key == K_ESCAPE:
running = False
ticks_to_next_ball -= 1
if ticks_to_next_ball <= 0:
ticks_to_next_ball = 25
ball_shape = add_ball(space)
balls.append(ball_shape)
screen.fill(THECOLORS["white"])
for ball in balls:
draw_ball(screen, ball)
draw_lines(screen, lines)
space.step(1/50.0)
pygame.display.flip()
clock.tick(50)
if __name__ == '__main__':
sys.exit(main())
5 - Joints (1)A static L shape is pretty boring. So lets make it a bit more exciting by adding two joints, one that it can rotate around, and one that prevents it from rotating too much. In this part we only add the rotation joint, and in the next we constrain it. As our static L shape won't be static anymore we also rename the function to add_L(). def add_L(space):
rotation_center_body = pm.Body(pm.inf, pm.inf) # 1
rotation_center_body.position = (300,300)
body = pm.Body(10, 10000) # 2
body.position = (300,300)
l1 = pm.Segment(body, (-150, 0), (255.0, 0.0), 5.0)
l2 = pm.Segment(body, (-150.0, 0), (-150.0, 50.0), 5.0)
rotation_center_joint = pm.PinJoint(body, rotation_center_body, (0,0), (0,0)) # 3
space.add(l1, l2, body, rotation_center_joint)
return l1,l2
To make it easy to see the point we draw a little red ball in its center pygame.draw.circle(screen, THECOLORS["red"], (300,300), 5) In a bigger program you will want to get the rotation_center_body.position instead of my little cheat here with (300,300), but it will work for this tutorial as the rotation center is static. 6 - Joints (2)In the previos part we added a pin joint, and now its time to contrain the rotating L shape to create a more interesting simulation. In order to do this we modify the add_L() function: def add_L(space):
rotation_center_body = pm.Body(pm.inf, pm.inf)
rotation_center_body.position = (300,300)
rotation_limit_body = pm.Body(pm.inf, pm.inf) # 1
rotation_limit_body.position = (200,300)
body = pm.Body(10, 10000)
body.position = (300,300)
l1 = pm.Segment(body, (-150, 0), (255.0, 0.0), 5.0)
l2 = pm.Segment(body, (-150.0, 0), (-150.0, 50.0), 5.0)
rotation_center_joint = pm.PinJoint(body, rotation_center_body, (0,0), (0,0))
joint_limit = 25
rotation_limit_joint = pm.SlideJoint(body, rotation_limit_body, (-100,0), (0,0), 0, joint_limit) # 2
space.add(l1, l2, body, rotation_center_joint, rotation_limit_joint)
return l1,l2
And to make it a bit more clear, we draw a circle to do symbolise the joint with a green circle with its radius set to the joint max: pygame.draw.circle(screen, THECOLORS["green"], (200,300), 25, 2) 7 - The endYou might notice that we never delete balls. This will make the simlation require more and more memory, and this is of course not what we want. So in the final step we add some code to remove balls from the simulation when they are bellow the screen. balls_to_remove = []
for ball in balls:
if ball.body.position.y < 0: # 1
balls_to_remove.append(ball) # 2
draw_ball(screen, ball)
for ball in balls_to_remove:
space.remove(ball, ball.body) # 3
balls.remove(ball) # 4
And now, done! You should have a inverted L shape in the middle of the screen being filled will balls, tipping over releasing them, tipping back and start over. You can check demo_slide_and_pinjoint.py included in pymunk, but it don't follow this tutorial exactly as I factored out a couple of blocks to functions to make it easier to follow in tutorial form. The full code for this tutorial is: import sys, random
import pygame
from pygame.locals import *
from pygame.color import *
import pymunk as pm
import math
def to_pygame(p):
"""Small hack to convert pymunk to pygame coordinates"""
return int(p.x), int(-p.y+600)
def add_ball(space):
"""Add a ball to the given space at a random position"""
mass = 1
radius = 14
inertia = pm.moment_for_circle(mass, 0, radius, (0,0))
body = pm.Body(mass, inertia)
x = random.randint(120,380)
body.position = x, 550
shape = pm.Circle(body, radius, (0,0))
space.add(body, shape)
return shape
def draw_ball(screen, ball):
"""Draw a ball shape"""
p = int(ball.body.position.x), 600-int(ball.body.position.y)
pygame.draw.circle(screen, THECOLORS["blue"], p, int(ball.radius), 2)
def add_L(space):
"""Add a inverted L shape with two joints"""
rotation_center_body = pm.Body(pm.inf, pm.inf)
rotation_center_body.position = (300,300)
rotation_limit_body = pm.Body(pm.inf, pm.inf) # 1
rotation_limit_body.position = (200,300)
body = pm.Body(10, 10000)
body.position = (300,300)
l1 = pm.Segment(body, (-150, 0), (255.0, 0.0), 5.0)
l2 = pm.Segment(body, (-150.0, 0), (-150.0, 50.0), 5.0)
rotation_center_joint = pm.PinJoint(body, rotation_center_body, (0,0), (0,0))
joint_limit = 25
rotation_limit_joint = pm.SlideJoint(body, rotation_limit_body, (-100,0), (0,0), 0, joint_limit) # 3
space.add(l1, l2, body, rotation_center_joint, rotation_limit_joint)
return l1,l2
def draw_lines(screen, lines):
"""Draw the lines"""
for line in lines:
body = line.body
pv1 = body.position + line.a.rotated(math.degrees(body.angle))
pv2 = body.position + line.b.rotated(math.degrees(body.angle))
p1 = to_pygame(pv1)
p2 = to_pygame(pv2)
pygame.draw.lines(screen, THECOLORS["lightgray"], False, [p1,p2])
def main():
pygame.init()
screen = pygame.display.set_mode((600, 600))
pygame.display.set_caption("Joints. Just wait and the L will tip over")
clock = pygame.time.Clock()
running = True
pm.init_pymunk()
space = pm.Space()
space.gravity = (0.0, -900.0)
lines = add_L(space)
balls = []
ticks_to_next_ball = 10
while running:
for event in pygame.event.get():
if event.type == QUIT:
running = False
elif event.type == KEYDOWN and event.key == K_ESCAPE:
running = False
ticks_to_next_ball -= 1
if ticks_to_next_ball <= 0:
ticks_to_next_ball = 25
ball_shape = add_ball(space)
balls.append(ball_shape)
screen.fill(THECOLORS["white"])
balls_to_remove = []
for ball in balls:
if ball.body.position.y < 150:
balls_to_remove.append(ball)
draw_ball(screen, ball)
for ball in balls_to_remove:
space.remove(ball, ball.body)
balls.remove(ball)
draw_lines(screen, lines)
pygame.draw.circle(screen, THECOLORS["red"], (300,300), 5)
pygame.draw.circle(screen, THECOLORS["green"], (200,300), 25, 2)
space.step(1/50.0)
pygame.display.flip()
clock.tick(50)
if __name__ == '__main__':
sys.exit(main())
|
Sign in to add a comment
Why not define to_pygame() like this?
def to_pygame(p):
The reason why I defined the y as -p.y + 600 was that I thought it was closer to how you think and therefor easier to understand. We want to flip the y coordinate (-p.y), and then offset it with the screen height (+600). Maybe it was a bad idea as some will wonder why it isnt written in the more optimal form 600-p.y..