Experiments with ruby-processing (processing-2.2.1) and JRubyArt for processing-3.0

Sunday, 5 May 2013

Using Shiffmans PBox2D library in ruby processing

Recently I've published some sketches using the fisica library (using this library in ruby-processing is slightly problematic owing to a somewhat overly complicated polymorphic structure, and a protected abstract class that is supposed to provide "public" methods), here is a look at the Shiffman library that does essentially the same thing (as fisica) by providing a wrapper for the JBox2D java physics library. However it might be just as easy for rubyists to work directly with JBox2D, a note of caution you can't have both the fisica and the pbox2d libraries installed at he same time. Anyway it did at least provide an interesting exercise converting the following example to ruby-processing. Features to note are the required syntax to call the java constants STATIC and DYNAMIC, ie precede constant with '::' and not a '.', and the need to explicitly "to_java" required in the set "Array of Vec2" argument CustomShape class. Also it appears since we are including the library inside the Processing sketch/module we do no seem to require to include Processing::Proxy in the classes.
# The Nature of Code
# <http://www.shiffman.net/teaching/nature>
# Spring 2011
# PBox2D example

# Basic example of falling rectangles
load_library :pbox2d
load_library :custom_shape

# module B2D is a wrapper for java imports, and Boundary and CustomShape classes
include B2D

attr_reader :box2d, :boundaries, :polygons

def setup
  size(640,360)
  smooth
  # Initialize box2d physics and create the world
  @box2d = PBox2D.new(self)
  box2d.create_world
  # We are setting a custom gravity
  box2d.set_gravity(0, -20)
  # Create Arrays 
  @polygons = []
  @boundaries = []
  # Add a bunch of fixed boundaries
  boundaries << Boundary.new(box2d, width / 4, height - 5, width/2 - 50, 10, 0)
  boundaries << Boundary.new(box2d, 3*width / 4, height - 50, width/2 - 50, 10, 0)
  boundaries << Boundary.new(box2d, width - 5,height / 2, 10, height, 0)
  boundaries << Boundary.new(box2d, 5, height / 2, 10, height, 0)
end

def draw
  background(255)
  # We must always step through time!
  box2d.step
  # Display all the boundaries
  boundaries.each do |wall|
    wall.display
  end

  # Display all the polygons
  polygons.each do |cs|
    cs.display
  end

  # polygons that leave the screen, we delete them
  # (note they have to be deleted from both the box2d world and our list
  polygons.each_with_index do |polygon, i|
    if polygon.done
      polygons.delete_at(i)
    end
  end
end

def mouse_pressed
  polygons << CustomShape.new(box2d, mouse_x, mouse_y)
end

Here are the supporting classes, wrapped as a ruby module in a "library" custom_shape.rb, note the B2D prefix is needed in the module wrapped classes to access the "include_package" java packages, this is a jruby 'feature' they have not been able to get round as yet.
module B2D
  include_package 'pbox2d'
  include_package 'org.jbox2d.collision.shapes'
  include_package 'org.jbox2d.common'
  include_package 'org.jbox2d.dynamics'
  java_import 'pbox2d.PBox2D'


  # The Nature of Code
  # <http://www.shiffman.net/teaching/nature>
  # Spring 2011
  # PBox2D example
  # A rectangular box

  class CustomShape

    # We need to keep track of a Body and a width and height
    attr_reader :body, :box2d

    # Constructor
    def initialize(b2d, x, y)
      # Add the box to the box2d world
      @box2d = b2d
      make_body(B2D::Vec2.new(x, y))
    end

    # This function removes the particle from the box2d world
    def kill_body!
      box2d.destroy_body(body)
    end

    # Is the particle ready for deletion?
    def done
      # Let's find the screen position of the particle
      pos = box2d.get_body_pixel_coord(body)
      # Is it off the bottom of the screen?
      if (pos.y > $app.height)
        kill_body!
        return true
      end
      return false
    end

    # Drawing the box
    def display
      # We look at each body and get its screen position
      pos = box2d.get_body_pixel_coord(body)
      # Get its angle of rotation
      a = body.get_angle

      f = body.get_fixture_list
      ps = f.get_shape


      rect_mode(CENTER)
      push_matrix
      translate(pos.x, pos.y)
      rotate(-a)
      fill(175)
      stroke(0)
      begin_shape
        # For every vertex, convert to pixel vector
        ps.get_vertex_count.times do |i|
          v = box2d.vector_world_to_pixels(ps.get_vertex(i))
          vertex(v.x, v.y)
        end
      end_shape(CLOSE)
      pop_matrix
    end

    # This function adds the rectangle to the box2d world
    def make_body(center)

      # Define a polygon (this is what we use for a rectangle)
      sd = B2D::PolygonShape.new

      vertices = []
      vertices << box2d.vector_pixels_to_world(B2D::Vec2.new(-15, 25))
      vertices << box2d.vector_pixels_to_world(B2D::Vec2.new(15, 0))
      vertices << box2d.vector_pixels_to_world(B2D::Vec2.new(20, -15))
      vertices << box2d.vector_pixels_to_world(B2D::Vec2.new(-10, -10))
      sd.set(vertices.to_java(Java::OrgJbox2dCommon::Vec2), vertices.length)

      # Define the body and make it from the shape
      bd = B2D::BodyDef.new
      bd.type = B2D::BodyType::DYNAMIC
      bd.position.set(box2d.coord_pixels_to_world(center))
      @body = box2d.create_body(bd)

      body.create_fixture(sd, 1.0)


      # Give it some initial random velocity
      body.set_linear_velocity(Vec2.new(rand(-5 .. 5), rand(2 .. 5)))
      body.set_angular_velocity(rand(-5 .. 5))
    end
  end

  # The Nature of Code
  # <http://www.shiffman.net/teaching/nature>
  # Spring 2012
  # PBox2D example

  # A fixed boundary class (now incorporates angle)



  class Boundary

    attr_reader :box2d, :b, :x, :y, :w, :h #, :a

    def initialize(b2d, x, y, w, h, a)
      @box2d = b2d
      @x = x
      @y = y
      @w = w
      @h = h

      # Define the polygon
      sd = B2D::PolygonShape.new

      # Figure out the box2d coordinates
      box2dW = box2d.scalar_pixels_to_world(w/2)
      box2dH = box2d.scalar_pixels_to_world(h/2)
      # We're just a box
      sd.set_as_box(box2dW, box2dH)


      # Create the body
      bd = B2D::BodyDef.new
      bd.type = B2D::BodyType::STATIC
      bd.angle = a
      bd.position.set(box2d.coord_pixels_to_world(x,y))
      @b = box2d.create_body(bd)

      # Attached the shape to the body using a Fixture
      b.create_fixture(sd,1)
    end

    # Draw the boundary, it doesn't move so we don't have to ask the Body for location
    def display
      fill(0)
      stroke(0)
      stroke_weight(1)
      rect_mode(CENTER)
      a = b.get_angle
      push_matrix
      translate(x,y)
      rotate(-a)
      rect(0,0,w,h)
      pop_matrix
    end

  end
end

No comments:

Post a Comment

Followers

Blog Archive

About Me

My photo
I have developed JRubyArt and propane new versions of ruby-processing for JRuby-9.1.5.0 and processing-3.2.2