Skip to content

Instantly share code, notes, and snippets.

@tabreturn
Last active November 5, 2020 03:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tabreturn/cce7083e944b95d3bd1ffd4f48700c8e to your computer and use it in GitHub Desktop.
Save tabreturn/cce7083e944b95d3bd1ffd4f48700c8e to your computer and use it in GitHub Desktop.
- processing.py + pypybox2d

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.

The engine has been ported to many other programming languages and environments, including Java, Adobe Flash (in ActionScript and Haxe languages), C#, Lua, JavaScript, and D. Bindings exist to use the compiled library from Python, DarkBASIC and BBC_BASIC.

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.

--https://en.wikipedia.org/wiki/Box2D

Box2D Task

  1. create a new sketch named "physics"
  2. download pypybox2d, extract it and move the pypybox2d directory into the sketch directory
  3. download the font Ernest and copy ernest.ttf into the sketch data directory
  4. import pypybox2d
    import pypybox2d as b2
    from pypybox2d.common import *
  5. explain that we will be using a pure python physics library (no C bindings) and processing, meaning it won't be super-performant
  6. scaffold sketch:
    def setup():
        size(600,600)
        frameRate(60)
        ernest = createFont('Ernest.ttf', 3)
        textFont(ernest)
    
    def draw():
        background('#004477')
        noFill()
  7. create world:
    def setup():
        ...
        global world
        world = b2.World(gravity=(0,-9.8))
  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']
        )
  9. 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
  10. 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()
  11. 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[0].user_data[1])
        polygon(ground.fixtures[0].shape._vertices)
        popMatrix()
  12. note that we could expand on this polygon function further (using user_data), having it handle the translate, fill, etc.
  13. 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[0].user_data[1])
        translate(ball.position.x, ball.position.y)
        rotate(ball.angle)
        r = ball.fixtures[0].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))
  14. 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[0].user_data[1])
        translate(trapezoid.position.x, trapezoid.position.y)
        rotate(trapezoid.angle)
        polygon(trapezoid.fixtures[0].shape._vertices)
        popMatrix()
  15. 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[0])
                print('fixture b: ' + contact.fixture_b.user_data[0])
  16. run the sketch; note the printed output
  17. now, change up the code, changing object colours on contact:
                ...
    
                bf = ball.fixtures[0]
    
                if contact.fixture_a.user_data[0] == 'ground':
                    bf.user_data[1] = ground.fixtures[0].user_data[1]
                elif contact.fixture_a.user_data[0] == 'trapezoid':
                    bf.user_data[1] = trapezoid.fixtures[0].user_data[1]
  18. 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

Calculation Functions

These may or may not be useful.

  • Calculation functions
  • sq(), sqrt(), pow(), norm(), lerp(), map()
  • abs(), ceil(), floor(), round(), min(), max(), constrain()

For usage, check the Calculation section in the reference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment