Processing.py + pypybox2d (Physics Library)
What is Box2d?
Box2D is a free open source 2-dimensional physics simulator engine written in C++ by Erin Catto and published under the zlib license. It has been used in Crayon Physics Deluxe, Limbo, Rolando, Incredibots, Angry Birds, Tiny Wings, Shovel Knight, Transformice, Happy Wheels, and many online Flash games, as well as iPhone, iPad and Android games using the Cocos2d or Moscrif game engine and Corona framework.
Box2D performs constrained rigid body simulation. It can simulate bodies composed of convex polygons, circles, and edge shapes. Bodies are joined together with joints and acted upon by forces. The engine also applies gravity, friction, and restitution.
Box2D's collision detection and resolution system consists of three pieces: an incremental sweep and prune broadphase, a continuous collision detection unit, and a stable linear-time contact solver. These algorithms allow efficient simulations of fast bodies and large stacks without missing collisions or causing instabilities.
- create a new sketch named "physics"
- download pypybox2d, extract it and move the pypybox2d directory into the sketch directory
- download the font Ernest and copy ernest.ttf into the sketch data directory
- import pypybox2d
import pypybox2d as b2 from pypybox2d.common import *
- explain that we will be using a pure python physics library (no C bindings) and processing, meaning it won't be super-performant
- scaffold sketch:
def setup(): size(600,600) frameRate(60) ernest = createFont('Ernest.ttf', 3) textFont(ernest) def draw(): background('#004477') noFill()
- create world:
def setup(): ... global world world = b2.World(gravity=(0,-9.8))
- add a static body (one that does not move), and draw a polygon within it:
def setup(): ... # create bodies global ground ground = world.create_static_body( position = (20, 20) ) ground.create_polygon_fixture( vertices = [(0,0), (20,0), (20,10), (0,10)], user_data = ['ground', '#FF9900'] )
- setup the step for the world (iterations are for accuracy, but it's better to keep these low and the framerate high)
def draw(): ... world.step(1.0/frameRate, 10, 10) # (time_step, velocity_iterations, position_iterations) world.clear_forces() scale(1,-1) # vetically flip the display to match the box2d y-axis (opposite of processing) translate(0,-height) # after flipping, offset the y-position to bring the shapes back into view scale(10) # box2d is tuned for meters-kilogram-second units -- thus works best for sizes between soup cans and buses
- create a marker to help orient students:
def draw(): ... # x/y corner marker stroke('#0099FF'); strokeWeight(3.0/10); fill('#0099FF') pushMatrix() scale(1,-1); text('x:1, y:1',2,-3); line(1,-1,1,-10); line(1,-1,12,-1) popMatrix() noFill(); noStroke()
- render the ground using the relevant properties; point out that the physics library is completely independent of processing -- you can use it for any graphic engines, and writing a 'wrapper' could clean this up a bit; we'll make a function to help out:
def polygon(vertices): beginShape() for v in vertices: vertex(v.x, v.y) endShape(CLOSE) def draw(): ... # render bodies pushMatrix() translate(ground.position.x, ground.position.y) fill(ground.fixtures.user_data) polygon(ground.fixtures.shape._vertices) popMatrix()
- note that we could expand on this polygon function further (using user_data), having it handle the translate, fill, etc.
- now, add a ball (a dynamic body); note the balls force vector pushes it rightwards; the ball is rendered with a line to indicate rotation:
def setup(): ... global ball ball = world.create_dynamic_body( position=(20, 55) ) ball.create_circle_fixture( 1, density=1.0, user_data = ['ball', '#FF0000'] ) def draw(): ... pushMatrix() fill(ball.fixtures.user_data) translate(ball.position.x, ball.position.y) rotate(ball.angle) r = ball.fixtures.shape.radius; circle(0, 0, r*2) stroke('#FFFFFF'); strokeWeight(3.0/10); line(0,0,0,0.8); noStroke() popMatrix() ball.apply_force_to_center(force=(5,0))
- now, add another dynamic body; note the one-off linear impulse (like a kick or punch force) that is applied off-centre to impart a spinning motion:
def setup(): ... global trapezoid trapezoid = world.create_dynamic_body( position=(24, 40), #angle = HALF_PI ) trapezoid.create_polygon_fixture( vertices = [(-1,0), (8,0), (6,6), (0,6)], density = 0.1, friction = 0.1, user_data = ['trapezoid', '#00FF00'] ) trapezoid.apply_linear_impulse(impulse=(1,0), point=(10,10)) def draw(): ... pushMatrix() fill(trapezoid.fixtures.user_data) translate(trapezoid.position.x, trapezoid.position.y) rotate(trapezoid.angle) polygon(trapezoid.fixtures.shape._vertices) popMatrix()
- add some collision rules:
def draw(): ... # collisions cm = world.contact_manager for contact in ball.contact_list: if contact.touching: print('fixture a: ' + contact.fixture_a.user_data) print('fixture b: ' + contact.fixture_b.user_data)
- run the sketch; note the printed output
- now, change up the code, changing object colours on contact:
... bf = ball.fixtures if contact.fixture_a.user_data == 'ground': bf.user_data = ground.fixtures.user_data elif contact.fixture_a.user_data == 'trapezoid': bf.user_data = trapezoid.fixtures.user_data
- show off some bounciness and another impulse using the ball:
... ball.create_circle_fixture( 1, density=1.0, restitution=0.5, user_data = ['ball', '#FF0000'] ) ball.apply_linear_impulse(impulse=(0,50), point=(20,0))
Looking for a project to experiment with physics? Maybe try recreate a pocket game: http://worldofstuart.excellentcontent.com/pocketeers/index.htm
These may or may not be useful.
- Calculation functions
For usage, check the Calculation section in the reference.