The recently released pbox2d-0.6.0.gem is an exemplar for how to create and use custom library gems in JRubyArt. The pbox2d gem provides a ruby wrapper for the jbox2d physics library, that make it easy to use the physics library in JRubyArt. The gem build now uses maven to download and install jbox2d. It features such usage refinements as stepping through the the physics (in a hidden pre() loop) so you don’t have to. See simple liquidy sketch example below (more examples are included in the gem):-


require 'pbox2d'
require_relative 'lib/particle_system'
require_relative 'lib/boundary'
attr_reader :box2d, :boundaries, :systems

Vect = Struct.new(:x, :y)

def setup
  sketch_title 'Liquidy'
  @box2d = WorldBuilder.build(app: self, gravity: [0, -20])
  @systems = []
  @boundaries = [
    Boundary.new(box2d, Vect.new(50, 100), Vect.new(300, 5), -0.3),
    Boundary.new(box2d, Vect.new(250, 175), Vect.new(300, 5), 0.5)
  ]
end

def draw
  background(255)
  # Run all the particle systems
  if systems.size > 0
    systems.each do |system|
      system.run
      system.add_particles(box2d, rand(0..2))
    end
  end
  # Display all the boundaries
  boundaries.each(&:display)
end

def mouse_pressed
  # Add a new Particle System whenever the mouse is clicked
  systems << ParticleSystem.new(box2d, 0, mouse_x, mouse_y)
end

def settings
  size(400, 300)
end

The Boundary Class using include Processing::Proxy to mimic java inner class


class Boundary
  include Processing::Proxy
  attr_reader :box2d, :b, :pos, :size, :a

  def initialize(b2d, pos, sz, a = 0)
    @box2d, @pos, @size, @a = b2d, pos, sz, a
    # Define the polygon
    sd = PolygonShape.new
    # Figure out the box2d coordinates
    box2d_w = box2d.scale_to_world(size.x / 2)
    box2d_h = box2d.scale_to_world(size.y / 2)
    # We're just a box
    sd.set_as_box(box2d_w, box2d_h)
    # Create the body
    bd = BodyDef.new
    bd.type = BodyType::STATIC
    bd.angle = a
    bd.position.set(box2d.processing_to_world(pos.x, pos.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(pos.x, pos.y)
    rotate(-a)
    rect(0, 0, size.x,size.y)
    pop_matrix
  end
end

The Particle System file, using forwardable instead of inheritance to get enumerable behaviour, note the use of a custom runnable module.


require 'forwardable'

module Runnable
  def run
    reject!(&:done)
    each(&:display)
  end
end

class ParticleSystem
  include Enumerable, Runnable
  extend Forwardable
  def_delegators(:@particles, :each, :reject!, :<<, :empty?)
  def_delegator(:@particles, :empty?, :dead?)

  attr_reader :x, :y

  def initialize(bd, num, x, y)
    @particles = []          # Initialize the Array
    @x, @y = x, y            # Store the origin point
    num.times do
      self << Particle.new(bd, x, y)
    end
  end

  def add_particles(bd, n)
    n.times do
      self << Particle.new(bd, x, y)
    end
  end
end

# A Particle
require 'pbox2d'

class Particle
  include Processing::Proxy
  TRAIL_SIZE = 6
  # We need to keep track of a Body

  attr_reader :trail, :body, :box2d

  # Constructor
  def initialize(b2d, x, y)
    @box2d = b2d
    @trail = Array.new(TRAIL_SIZE, [x, y])
    # Add the box to the box2d world
    # Here's a little trick, let's make a tiny tiny radius
    # This way we have collisions, but they don't overwhelm the system
    make_body(x, y, 0.2)
  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.body_coord(body)
    # Is it off the bottom of the screen?
    return false unless (pos.y > box2d.height + 20)
    kill_body
    true
  end

  # Drawing the box
  def display
    # We look at each body and get its screen position
    pos = box2d.body_coord(body)
    # Keep track of a history of screen positions in an array
    (TRAIL_SIZE - 1).times do |i|
      trail[i] = trail[i + 1]
    end
    trail[TRAIL_SIZE - 1] = [pos.x, pos.y]
    # Draw particle as a trail
    begin_shape
    no_fill
    stroke_weight(2)
    stroke(0, 150)
    trail.each do |v|
      vertex(v[0], v[1])
    end
    end_shape
  end

  # This function adds the rectangle to the box2d world
  def make_body(x, y, r)
    # Define and create the body
    bd = BodyDef.new
    bd.type = BodyType::DYNAMIC
    bd.position.set(box2d.processing_to_world(x, y))
    @body = box2d.create_body(bd)
    # Give it some initial random velocity
    body.set_linear_velocity(Vec2.new(rand(-1.0..1), rand(-1.0..1)))
    # Make the body's shape a circle
    cs = CircleShape.new
    cs.m_radius = box2d.scale_to_world(r)
    fd = FixtureDef.new
    fd.shape = cs
    fd.density = 1
    fd.friction = 0  # Slippery when wet!
    fd.restitution = 0.5
    # We could use this if we want to turn collisions off
    # cd.filter.groupIndex = -10
    # Attach fixture to body
    body.create_fixture(fd)
  end
end