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

Sunday 28 September 2014

Running ribiprocessing from netbeans

Above is a screenshot of me running seeking_neural.rb from Netbeans using ribiprocessing,a really lighweight wrapper for processing. To do this all I needed was the netbeans ruby plugin and then to install (to netbeans) the local gem ribiprocessing-0.1.0.gem.

Thursday 25 September 2014

Light-weight ruby-processing, heavy-weight example

Recently I had cause to mess with Philip Cunninghams Ribiprocessing (a light-weight ruby-processing, I've beefed it up a bit with some helper methods and Vec2D and Deglut jruby extensions). Here is a ruby-processing sketch I translated to work on my modified ribiprocessing (makes use of range clip function, a replacement for processing constrain):-
# Based on SeekingNeural example by Daniel Shiffman
# The Nature of Code
# http://natureofcode.com

require 'ribiprocessing'

module SeekingNeural

  class Perceptron
    # Perceptron is created with n weights and learning constant
    def initialize(n, c)
      @weights = Array.new(n) { rand(0..1.0) }
      @c = c
    end

    # Function to train the Perceptron
    # Weights are adjusted based on vehicle's error
    def train(forces, error)
      trained = @weights.zip(forces.map { |f| f.to_a }
        .map { |a, b| (a * error.x + b * error.y) * @c })
      .map { |w, c| (0.0 .. 1.0).clip(w + c)  }
      @weights = trained
    end

    # Give me a steering result
    def feedforward(forces)
      # Sum all values
      forces.zip(@weights).map { |a, b| a * b }.reduce(Vec2D.new, :+)
      # forces.zip(@weights).map { |a, b| a * b }.reduce(:+)
    end
  end

  # Seek
  # Daniel Shiffman <http://www.shiffman.net>

  class Vehicle
    MAX_SPEED = 4
    MAX_FORCE = 0.1

    attr_reader :brain, :sz, :location, :targets, :desired

    def initialize(n, x, y)
      @brain = Perceptron.new(n, 0.001)
      @acceleration = Vec2D.new
      @velocity = Vec2D.new
      @location = Vec2D.new(x, y)
      @sz = 6.0
    end

    # Method to update location
    def update(width, height)
      # Update velocity
      @velocity += @acceleration
      # Limit speed
      @velocity.set_mag(MAX_SPEED) { @velocity.mag > MAX_SPEED }
      @location += @velocity
      # Reset acceleration to 0 each cycle
      @acceleration *= 0
      @location.x = (0 .. width).clip location.x
      @location.y = (0 .. height).clip location.y
    end

    def apply_force(force)
      # We could add mass here if we want A = F / M
      @acceleration += force
    end

    # Here is where the brain processes everything
    def steer(targets, desired)
      # Steer towards all targets
      forces = targets.map { |target| seek(target) }
      # That array of forces is the input to the brain
      result = brain.feedforward(forces)
      # Use the result to steer the vehicle
      apply_force(result)
      # Train the brain according to the error
      error = desired - location
      brain.train(forces, error)
    end

    # A method that calculates a steering force towards a target
    # STEER = DESIRED MINUS VELOCITY
    def seek(target)
      desired = target - location  # A vector pointing from the location to the target
      # Normalize desired and scale to the maximum speed
      desired.normalize!
      desired *= MAX_SPEED
      # Steering = Desired minus velocity
      steer = desired - @velocity
      steer.set_mag(MAX_FORCE) { steer.mag > MAX_FORCE } # Limit to a maximum steering force
      steer
    end

    def display(app)
      # Draw a triangle rotated in the direction of velocity
      theta = @velocity.heading + Math::PI / 2
      app.fill(175)
      app.stroke(0)
      app.stroke_weight(1)
      app.push_matrix
      app.translate(location.x, location.y)
      app.rotate(theta)
      app.begin_shape
      app.vertex(0, -sz)
      app.vertex(-sz * 0.5, sz)
      app.vertex(sz * 0.5, sz)
      app.end_shape(Java::ProcessingCore.PConstants::CLOSE)
      app.pop_matrix
    end
  end
end


class Seeking < Ribiprocessing::SimpleApp
  include SeekingNeural

  # A Vehicle controlled by a Perceptron
  attr_reader :targets, :desired, :v


  def setup
    size(640, 360)
    # The Vehicle's desired location
    @desired = Vec2D.new(width / 2, height / 2)

    # Create a list of targets
    make_targets

    # Create the Vehicle (it has to know about the number of targets
    # in order to configure its brain)
    @v = Vehicle.new(targets.size, rand(width), rand(height))
  end

  # Make a random ArrayList of targets to steer towards
  def make_targets
    @targets = Array.new(8) { Vec2D.new(rand(width), rand(height)) }
  end

  def draw
    background(255)

    # Draw a circle to show the Vehicle's goal
    stroke(0)
    stroke_weight(2)
    fill(0, 100)
    ellipse(desired.x, desired.y, 36, 36)

    # Draw the targets
    targets.each do |target|
      no_fill
      stroke(0)
      stroke_weight(2)
      ellipse(target.x, target.y, 16, 16)
      line(target.x, target.y - 16, target.x, target.y + 16)
      line(target.x - 16, target.y, target.x + 16, target.y)
    end

    # Update the Vehicle
    v.steer(targets, desired)
    v.update(width, height)
    v.display self
  end

  def mouse_pressed
    make_targets
  end
end

Seeking.new title: 'Seeking Neural'


Update 27 September 2014, turns out ribiprocessing was a project that Philip Cunnigham created as university project but abandoned in favor of clojure!!! Didn't take too much to give it some pretty decent functionality. Update 4 October 2014, I have gone quite a further created a new ruby-processing implementation JRubyArt.

Saturday 20 September 2014

Using libraries from processing-3.0a4 in ruby processing

Up to and including processing-2.2.1 the path to sketchbook was/is discovered by "reading" the preferences.txt configuration file for ruby processing. For processing-3.0a4 the name has changed (to allow different libraries/sketches to co-exist for different versions?). For processing-3.0a4 you can set the "sketchbook_path" in the ".rp5rc" file, works for me on linux see yaml file below (will override preferences.txt value).
---
PROCESSING_ROOT: /home/tux/processing-3.0a4
JRUBY: "true"
sketchbook_path: /home/tux/sketchbook

Wednesday 17 September 2014

Using the Range class in ruby processing

Since ruby-processing-2.6.3 the processing constrain function is actually implemented using the clip function of Range (this method has been added to Range in ruby-processing). However in ruby processing we should probably prefer to use the clip method directly, as I have here in my World class:-
# Class provides an OO way constraining a Mover in a 2D space
# use
# world = World.new((0..width), (0..height))
# world.constrain_mover(mover)
class World
  attr_reader :xrange, :yrange
  def initialize(xrange, yrange)
    @xrange, @yrange = xrange, yrange
  end

  # @param mover is expected respond to loc, vel
  # that in turn respond to x and y getter/setters (Vec2D does this)

  def constrain_mover(mover)
    # Note clip functionality, extends Range in ruby-processing
    unless xrange.cover? mover.loc.x
      mover.vel.x *= -1
      mover.loc.x = xrange.clip mover.loc.x
    end
    return if yrange.cover? mover.loc.y
    mover.vel.y *= -1
    mover.loc.y = yrange.clip mover.loc.y
  end
end

For the sketch using this code see my fork Dan Shiffmans The Nature of Code Examples.

Monday 15 September 2014

Include Processing::Proxy to mimic the processing inner classes

If you have been using processing for some time, then you should be aware of the inner classes used by processing to make variables/methods available across classes (strictly non OOP but convenient). In the processing ide, the use of java inner classes is hidden (because the code is post-processed). However if you look at the generated java you will find that if you create a class in the processing ide, it becomes an inner class. Use of the inner class is there to allow easy access to the methods and variables of the outer class (your sketch). If you graduated from using the processing ide to using Eclipse (see tutorial) or Netbeans (other ides are available) you will know that when you create a new java class you can't use the methods and variables, unless you call the instance of the PApplet from you sketch (typically a PApplet variable parent created in your new class, and the class constructor is used to initialize that variable). To give a similar level of access to that provided by processings inner classes you should include the ruby processing Processing::Proxy module in your sketches (however sketch width, and height are not available). The more 'pure' way to to provide such access is to copy the "Eclipse" way of accessing these variables and methods, and that is what you "should" do to access sketch width and height. The "dirty" way to do it is access the sketches '$app' global variable "width = $app.width" for example. A template for creating Processing::Proxy class (in a separate file) is included in ruby-processing:-
rp5 create inner_class --inner

Of course you will give a different more relevant name to your inner classes!!
Then vim inner_class.rb, other editors are available

class InnerClass
  include Processing::Proxy
end

Thursday 4 September 2014

New features in ruby-processing-2.6.2

# Hooky class demonstrates how to use the post_initialize hook,
# available since ruby-processing-2.6.2, to add additional attribute
# in this case :background as an array of int (splat to color)
# Not sure how clever it is to have multiple sketch instances.

class Hooky < Processing::App
  attr_reader :back

  def setup
    size 200, 200
    background(*back)
  end

  def post_initialize(args)
    @back = (args[:background])
  end
end

red = [200, 0, 0]
green = [0, 200, 0]
blue = [0, 0, 200]
colors = [red, green, blue]

colors.each_with_index do |col, i|
  Hooky.new(x: i * 90, y: i * 90, title: "Hooky #{i}", background: col)
end


Read more about post-initialization hooks in POODR by Sandi Metz.

Monday 1 September 2014

Instrumenting ruby-processing

Enhance your geek credibility by using JMXBeans to monitor your ruby-processing sketch. See jruby wiki for details. Above was done using a modified version of JRubyArt (the development version of ruby-processing) where I sent --manage flag to jruby when running the sketch.

Getting started with ruby-processing for ruby purists

Previously in my post getting started for wizards I showed how to create a sketch that would be more familiar to people coming from processing to ruby-processing. The sketch does not need to be explicitly wrapped as a class (that extends from Processing::App), since ruby-processing does that for you under the hood. This is the DSL approach to writing ruby-processing sketches which I favour, and makes it easier to read across from other processing modes to ruby-processing. However since ruby-processing-2.6.0 it is just as easy to create a classical sketch.
rp5 create classic_sketch 200 200 --wrap
rp5 watch classic_sketch.rb


Open a new console and 'vim classic_sketch.rb' or us another suitable editor

class ClassicSketch < Processing::App
  def setup
    size 200, 200
  end

  def draw

  end
end

# ClassicSketch.new(x: 20, y: 20)

For instant gratification and to relocate the sketch on your display un-comment the last line of the sketch and save (also note this line was "not" required to run the sketch, rp5 run or rp5 watch takes care of creating a new instance of sketch for you). You only use this form to send parameters, such as 'x offset' to your sketch

class ClassicSketch < Processing::App
  def setup
    size 200, 200
  end

  def draw

  end
end

ClassicSketch.new(x: 20, y: 20)
The sketch updates auto-magically...
Already in the works for the next release is the possibility of placing the sketch on your screen using the config file '~/.rp5rc' so this is kind of redundant, but a post initialization hook might get added so you will be able to send all sorts of other parameters. Which are then available by overriding the post_initialization hook method (See Sandi Metz POODR)

Next change the background of your sketch

class ClassicSketch < Processing::App
  def setup
    size 200, 200
  end

  def draw
    background 0
  end
end

ClassicSketch.new(x: 20, y: 20)

Next create a blue box.

class ClassicSketch < Processing::App
  def setup
    size 200, 200
  end

  def draw
    background 0
    fill 0, 0, 200
    rect 40, 50, 120, 100
  end
end

ClassicSketch.new(x: 20, y: 20)


To create class wrapped P3D sketch just "rp5 create classy_sketch 200 200 p3d --wrap" actually it probably doesn't matter where you put the --wrap option after create (but the order of the other variables is important)...

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