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

Tuesday, 28 April 2015

Exploring the latest processing-3.0 with JRubyArt (with some success)

I have created a processing-3.0 branch to explore using the latest processing with JRubyArt and it is looking pretty good so far, JAVA2D was easy to get running but P2D and P3D modes took a bit longer. The key to running in the opengl modes was to putting the native binaries on the java.libary.path:-
jruby -Djava.library.path="/home/tux/lib/linux64" trefoil.rb
I needed to change the build to include the lwjgl jars and app.rb to accommodate the changes to processing but also unlike vanilla processing the sketch was also modified, replacing the applet convention of size(width, height, renderer) in setup with 3 methods (not required to be called in setup):-
# Old
  def setup
    size(1024, 768, P3D)
  end
#New
  def sketch_width
    1024
  end

  def sketch_height
    768
  end

  def sketch_renderer
    P3D
  end

Here is a working 'bare' JAVA2D sketch (run using k9 run keyboard.rb) which was tested with jruby-complete-9.0.0.0.pre2.
def setup
  @num_chars = 26
  @key_scale = 200.0 / @num_chars - 1.0
  @rect_width = width / 4
  no_stroke
  background 0
end

def draw
  return unless key_pressed? && ('A'..'z').include?(key)
  key_index = key.ord - (key <= 'Z' ? 'A'.ord : 'a'.ord)
  fill millis % 255
  begin_rect = map1d(key_index, (0..25), (0..width - @rect_width))
  rect begin_rect, 0, @rect_width, height
end

def sketch_width; 640; end

def sketch_height; 360; end

def sketch_renderer; JAVA2D; end

Rubocop will complain of single line definition, but here I think it makes sense! The default renderer is currently JAVA2D, but it looks like Ben is having some success with JavaFX so that might change v. soon. It should be possible to use the java Class loader to set the java.library.path as we do in load_library, but this will probably require 'k9 run sketch', however there is work to be done to even run anything other than Java2D with k9. So presently I think I will concentrate on jruby-9.0.0.0 work.....
Allowing for Ben / Andrés to progress processing-3.0, before I waste any more time.

Warning this has all changed in favour of settings method, and defining renderer along with size as single line entry ie like processing-2.0 but in settings, not setup... furthermore jruby class loader reverted to original and the design of JRubyArt mirrored ruby-processing

Saturday, 18 April 2015

Sandi Metz suspicion of nil (pbox2d example)

Late last year Sandi Metz blogged about Suspicions of nil, more recently she presented Nothing is Something at Bath ruby, and I just watched the video at confreaks. The timing could not be much better I had just translated Dan Shiffmans MouseJoint sketch to run with ruby-processing, and the Spring class which has all these egregious checks for null (or nil in initial ruby version). I found that by using ruby duck-typing I could create a DummySpring class, that got rid of all those suspicious null checks!!!
The DummySpring class
# dummy_spring.rb by Martin Prout
# using duck-typing so class can stand in for Spring

# Using this class avoids test for nil
class DummySpring
  def initialize; end

  # If it exists we set its target to the mouse location
  def update(_x, _y); end

  def display; end

  # This is the key function where
  # we attach the spring to an x,y location
  # and the Box object's location
  def bind(x, y, box)
    spring = Spring.new
    spring.bind(x, y, box)
    spring
  end

  def destroy; end
end

The only slightly tricky thing was the sketch needs to know that DummyString bind returns an instance of Spring, but otherwise it all looks pretty cool. Would make a good example except that pbox2d confounds things (we have two worlds a jbox2d physics world and a processing sketch world, and we track both).

Monday, 6 April 2015

Using Forwardable in place of Processing::Proxy to mimic java inner class

In the following example we are using the pbox2d gem (in place of Dan Shiffmans Box2D-for-Processing to provide jbox2d. Here is the translated sketch:-
# The Nature of Code
# PBox2D example translated to use pbox2d gem by Martin Prout, and to use
# forwardable instead of Processing::Proxy to mimic java inner class access
# An uneven surface

require 'pbox2d'
load_library :surface

attr_reader :surface, :box2d, :particles

def setup
  size(500, 300)
  smooth 4
  # Initialize box2d physics and create the world
  @box2d = Box2D.new(self)
  box2d.init_options(gravity: [0, -20])
  box2d.create_world
  # to later set a custom gravity
  # box2d.gravity([0, -20])
  # Create the empty list
  @particles = []
  # Create the surface
  @surface = Surface.new(self)
end

def draw
  # If the mouse is pressed, we make new particles
  # We must always step through time!
  background(138, 66, 54)
  # Draw the surface
  surface.display
  # NB ? reqd to call mouse_pressed value, else method gets called.
  particles << Particle.new(self, mouse_x, mouse_y, rand(2.0..6)) if mouse_pressed?
  # Draw all particles
  particles.each(&:display)
  # Particles that leave the screen, we delete them
  # (note they have to be deleted from both the box2d world and our list
  particles.reject!(&:done)
  # Just drawing the framerate to see how many particles it can handle
  fill(0)
  text(format('framerate: %d', frame_rate.to_i), 12, 16)
end

Here is the associated library that uses Forwardable in place of Processing::Proxy. Place surface.rb in "library/surface" folder, or modify the sketch to use require_relative:-
require 'forwardable'

# The Nature of Code
# PBox2D example
# An uneven surface boundary
class Surface
  extend Forwardable
  # We'll keep track of all of the surface points
  def_delegators(:@app, :box2d, :width, :height, :begin_shape,
                 :end_shape, :fill, :stroke, :stroke_weight,
                 :vertex, :map1d, :noise)
  attr_reader :surface, :body, :y

  def initialize(app)
    @app = app
    @surface = []
    # This is what box2d uses to put the surface in its world
    chain = ChainShape.new
    # Perlin noise argument
    xoff = 0.0
    # This has to go backwards so that the objects  bounce off the top of the
    # surface. This "edgechain" will only work in one direction!
    (width + 10).step(-10, -5) do |x|
      # Doing some stuff with perlin noise to calculate a surface that points
      # down on one side and up on the other
      if x > width / 2
        @y = 100 + (width - x) * 1.1 + map1d(noise(xoff), (0..1.0), (-80..80))
      else
        @y = 100 + x * 1.1 + map1d(noise(xoff), (0..1.0), (-80..80))
      end
      # Store the vertex in screen coordinates
      surface << Vec2.new(x, y)
      # Move through perlin noise
      xoff += 0.1
    end
    # Build an array of vertices in Box2D coordinates
    # from the ArrayList we made
    vertices = []
    surface.each do |surf|
      vertices << box2d.processing_to_world(surf)
    end
    # Create the chain!
    chain.createChain(vertices, vertices.length)
    # The edge chain is now attached to a body via a fixture
    bd = BodyDef.new
    bd.position.set(0.0, 0.0)
    @body = box2d.createBody(bd)
    # Shortcut, we could define a fixture if we
    # want to specify frictions, restitution, etc.
    body.createFixture(chain, 1)
  end

  # A simple function to just draw the edge chain as a series of vertex points
  def display
    stroke_weight(2)
    stroke(0)
    fill(135, 206, 250)
    begin_shape
    vertex(width, 0)          # extra vertices so we can fill sky
    surface.map { |v| vertex(v.x, v.y) }     # the mountain range
    vertex(0, 0)              # extra vertices so we can fill sky
    end_shape
  end
end

# Using forwardable again
class Particle
  extend Forwardable
  def_delegators(:@app, :box2d, :push_matrix, :pop_matrix, :rotate,
                 :translate, :fill, :stroke, :stroke_weight, :noise,
                 :map1d, :ellipse, :line)
  # We need to keep track of a Body

  attr_reader :body, :x, :y, :r

  # Constructor
  def initialize(app, x, y, r)
    @app, @x, @y, @r = app, x, y, r
    # This function puts the particle in the Box2d world
    make_body(x, y, r)
  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
    pos = box2d.body_coord(body)
    # Is it off the bottom of the screen?
    return false unless pos.y > box2d.height + r * 2
    kill_body
    true
  end

  def display
    # We look at each body and get its screen position
    pos = box2d.body_coord(body)
    # Get its angle of rotation
    a = body.get_angle
    push_matrix
    translate(pos.x,  pos.y)
    rotate(-a)
    fill(175)
    stroke(0)
    stroke_weight(1)
    ellipse(0, 0, r * 2, r * 2)
    # Let's add a line so we can see the rotation
    line(0, 0, r, 0)
    pop_matrix
  end

  # This function adds the particle to the box2d world
  def make_body(x, y, r)
    # Define and create the body
    bd = BodyDef.new
    bd.position = box2d.processing_to_world(x, y)
    bd.type = BodyType::DYNAMIC
    @body = box2d.create_body(bd)
    # Make the body's shape a circle
    cs = CircleShape.new
    cs.m_radius = box2d.scale_to_world(r)
    fd = FixtureDef.new
    fd.shape = cs
    # Parameters that affect physics
    fd.density = 1
    fd.friction = 0.01
    fd.restitution = 0.3
    # Attach fixture to body
    body.create_fixture(fd)
    # Give it a random initial velocity (and angular velocity)
    body.set_linear_velocity(Vec2.new(rand(-10..10), rand(5..10)))
    body.set_angular_velocity(rand(-10..10))
  end
end

Sunday, 5 April 2015

Replacing Processing::Proxy in ruby-processing

I have often thought that Processing::Proxy in ruby-processing is a bit of hack (further it doesn't always do what you need to mimic vanilla processings "inner" class access to methods/variables), here is a more respectable alternative using forwardable. As a bonus shows the neat Vec3D :to_vertex method in action:-
require 'forwardable'
load_libraries :icosahedron, :vecmath

def setup
  size 640, 360, P3D
  @ico1 = Icosahedron.new self, 75.0
  @ico2 = Icosahedron.new self, 75.0
  @ico3 = Icosahedron.new self, 75.0
end

def draw
  background 0
  lights
  translate(width / 2, height / 2)
  push_matrix
  translate(-width / 3.5, 0)
  rotate_x frame_count * PI / 185
  rotate_y frame_count * PI / -200
  stroke 170, 0, 0
  no_fill
  @ico1.draw
  pop_matrix
  push_matrix
  rotate_x frame_count * PI / 200
  rotate_y frame_count * PI / 300
  stroke 170, 0, 180
  fill 170, 170, 0
  @ico2.draw
  pop_matrix
  push_matrix
  translate(width / 3.5, 0)
  rotate_x frame_count * PI / -200
  rotate_y frame_count * PI / 200
  no_stroke
  fill 0, 0, 185
  @ico3.draw
  pop_matrix
end
Here is the library class that uses forwardable:-
# Example of replacing Processing::Proxy with extend Forwardable
# And using Vec3D :to_vertex function
class Icosahedron
  extend Forwardable   # replacing Processing::Proxy mixin
  def_delegators(:@app, :begin_shape, :end_shape)
  attr_reader :r, :app, :renderer

  def initialize(app, radius)
    @r = radius
    @app = app
    @renderer = AppRender.new(app)
  end

  ##
  # Draw an icosahedron defined by a radius r.
  #
  def draw
    # Calculate vertex data for an icosahedron inscribed by a sphere radius 'r'.
    # Use 4 Golden Ratio rectangles as the basis.
    phi = (1.0 + Math.sqrt(5.0)) / 2.0
    h = r / Math.sqrt(1.0 + phi * phi)
    v =
    [
      Vec3D.new(0, -h, h * phi), Vec3D.new(0, -h, -h * phi),
      Vec3D.new(0, h, -h * phi), Vec3D.new(0, h, h * phi),
      Vec3D.new(h, -h * phi, 0), Vec3D.new(h, h * phi, 0),
      Vec3D.new(-h, h * phi, 0), Vec3D.new(-h, -h * phi, 0),
      Vec3D.new(-h * phi, 0, h), Vec3D.new(-h * phi, 0, -h),
      Vec3D.new(h * phi, 0, -h), Vec3D.new(h * phi, 0, h)
    ]

    begin_shape(Java::ProcessingCore::PConstants::TRIANGLES)
    draw_triangle(v[0], v[7], v[4])
    draw_triangle(v[0], v[4], v[11])
    draw_triangle(v[0], v[11], v[3])
    draw_triangle(v[0], v[3], v[8])
    draw_triangle(v[0], v[8], v[7])
    draw_triangle(v[1], v[4], v[7])
    draw_triangle(v[1], v[10], v[4])
    draw_triangle(v[10], v[11], v[4])
    draw_triangle(v[11], v[5], v[10])
    draw_triangle(v[5], v[3], v[11])
    draw_triangle(v[3], v[6], v[5])
    draw_triangle(v[6], v[8], v[3])
    draw_triangle(v[8], v[9], v[6])
    draw_triangle(v[9], v[7], v[8])
    draw_triangle(v[7], v[1], v[9])
    draw_triangle(v[2], v[1], v[9])
    draw_triangle(v[2], v[10], v[1])
    draw_triangle(v[2], v[5], v[10])
    draw_triangle(v[2], v[6], v[5])
    draw_triangle(v[2], v[9], v[6])
    end_shape
  end

  def draw_triangle(p1, p2, p3)
    p1.to_vertex(renderer)
    p2.to_vertex(renderer)
    p3.to_vertex(renderer)
  end
end

Thursday, 2 April 2015

Using delegators in JRubyArt (where vanilla processing uses inner classes)

Using delegators in JRubyArt (where vanilla processing uses inner classes) and ruby-processing uses 'include Processing::Proxy' to mimic the same behaviour. See node.rb, where we selectively expose PApplets fill, stroke, stroke_weight and ellipse methods in the Node class.


force_directed_graph.rb
require 'jruby_art'
require 'toxiclibs'
require_relative 'cluster'
require_relative 'node'

#
# Copyright (c) 2010 Daniel Shiffman
#
# This demo & library is free software you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation either
# version 2.1 of the License, or (at your option) any later version.
#
# http://creativecommons.org/licenses/LGPL/2.1/
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
class ForceDirectedGraph < Processing::App
  attr_reader :physics, :clusters, :show_physics, :show_particles, :f

  def setup
    size(640, 360)
    @f = create_font('Georgia', 12, true)
    @show_physics = true
    @show_particles = true
    # Initialize the physics
    @physics = Physics::VerletPhysics2D.new
    physics.set_world_bounds(Toxi::Rect.new(10, 10, width - 20, height - 20))
    # Spawn a new random graph
    new_graph
  end

  # Spawn a new random graph
  def new_graph
    # Clear physics
    physics.clear
    center = TVec2D.new(width / 2, height / 2)
    @clusters = (0..8).map { Cluster.new(physics, rand(3..8), rand(20..100), center) }
    # All clusters connect to all clusters
    clusters.each_with_index do |ci, i|
      clusters[i + 1..clusters.size - 1].each do |cj|
        ci.connect(cj)
      end
    end
  end

  def draw
    # Update the physics world
    physics.update
    background(255)
    # Display all points
    clusters.each(&:display) if show_particles
    # If we want to see the physics
    if show_physics
      clusters.each_with_index do |ci, i|
        ci.internal_connections self
        # Cluster connections to other clusters
        clusters[1 + i..clusters.size - 1].each do |cj|
          ci.show_connections(self, cj)
        end
      end
    end
    # Instructions
    fill(0)
    text_font(f)
    text("'p' to display or hide particles\n'c' to display or hide connections\n'n' for new graph", 10, 20)
  end

  # Key press commands
  def key_pressed
    case key
    when 'c'
      @show_physics = !show_physics
      @show_particles = true unless show_physics
    when 'p'
      @show_particles = !show_particles
      @show_physics = true unless show_particles
    when 'n'
      new_graph
    end
  end
end

ForceDirectedGraph.new(title: 'Force directed graphs')

cluster.rb
#
# Copyright (c) 2010 Daniel Shiffman
#
# This demo & library is free software you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation either
# version 2.1 of the License, or (at your option) any later version.
#
# http://creativecommons.org/licenses/LGPL/2.1/
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
class Cluster
  attr_reader :nodes, :diameter, :physics

  # We initialize a with a number of nodes, a diameter, and centerpoint
  def initialize(physics, n, d, center)
    @physics = physics
    @diameter = d
    @nodes = (0..n).map { Node.new(center.add(TVec2D.random_vector)) }
    # Connect all the nodes with a Spring
    nodes[1..nodes.size - 1].each_with_index do |pi, i|
      nodes[0..i].each do |pj|
        physics.add_spring(Physics::VerletSpring2D.new(pi, pj, diameter, 0.01))
      end
    end
  end

  def display
    nodes.each(&:display)
  end
  # This functons connects one cluster to another
  # Each point of one cluster connects to each point of the other cluster
  # The connection is a "VerletMinDistanceSpring"
  # A VerletMinDistanceSpring is a spring which only enforces its rest length if the
  # current distance is less than its rest length. This is handy if you just want to
  # ensure objects are at least a certain distance from each other, but don't
  # care if it's bigger than the enforced minimum.

  def connect(other)
    other_nodes = other.nodes
    nodes.each do |pi|
      other_nodes.each do |pj|
        physics.add_spring(Physics::VerletMinDistanceSpring2D.new(pi, pj, (diameter + other.diameter) * 0.5, 0.05))
      end
    end
  end

  # Draw all the internal connections
  def internal_connections(app)
    app.stroke(200, 0, 0, 80)
    nodes[0..nodes.size - 1].each_with_index do |pi, i|
      nodes[i + 1..nodes.size - 1].each do |pj|
        app.line(pi.x, pi.y, pj.x, pj.y)
      end
    end
  end

  # Draw all the connections between this and another Cluster
  def show_connections(app, other)
    app.stroke(200, 200, 0, 20)
    app.stroke_weight(2)
    other_nodes = other.nodes
    nodes.each do |pi|
      other_nodes[0..other_nodes.size - 1].each do |pj|
        app.line(pi.x, pi.y, pj.x, pj.y)
      end
    end
  end
end

node.rb
require 'forwardable'

# The Nature of Code
# Daniel Shiffman
# http://natureofcode.com
# Force directed graph
# Heavily based on: http://code.google.com/p/fidgen/
# Notice how we are using inheritance here!
# We could have just stored a reference to a VerletParticle object
# inside the Node class, but inheritance is a nice alternative
class Node < Physics::VerletParticle2D
  extend Forwardable
  def_delegators(:@app, :fill, :stroke, :stroke_weight, :ellipse)
  def initialize(pos)
    super(pos)
    @app = $app
  end

  # All we're doing really is adding a display function to a VerletParticle
  def display
    fill(50, 200, 200, 150)
    stroke(50, 200, 200)
    stroke_weight(2)
    ellipse(x, y, 16, 16)
  end
end

Followers

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