Ruby Processing

Here is my blog in which I will describe my experiments with ruby-processing, find out more about ruby-processing at:- https://github.com/jashkenas/ruby-processing compatible with processing-2.2.1 and https://github.com/monkstone/cf3ruby for my version of the cfdg DSL (context-free-art)

Thursday, 21 August 2014

JRuby-9000 returns for testing


Well it seems that jruby-head is ready for testing again!!!
jruby --version
SNAPSHOT (2.1.2) 2014-08-20 8fb3b63 Java HotSpot(TM) 64-Bit Server VM 25.20-b15 on 1.8.0_20-ea-b15 [linux-amd64]

..... after a bit of short lived euphoria, it seems it is still broken after all (I was using jruby-complete-1.7.13) in my initial tests ('cos I'd configured it that way mea culpa) so jruby-head is failing to load jars, I'd seen that reported before, will have to look into it.....
However problem seems to be a failure to load yaml configuration from file, see irb (mri) and jirb (ruby-head) below:-
>> require 'psych'
=> true
>> RP_CONFIG = Psych.load_file("/home/tux/.rp5rc")
=> {"PROCESSING_ROOT"=>"/home/tux/processing-2.2.1", "JRUBY"=>"true"}
>>

tux@monkstone:~/Desktop$ jirb
Picked up _JAVA_OPTIONS: -Dswing.defaultlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel
>> require 'psych'
=> true
>> RP_CONFIG = Psych.load_file("/home/tux/.rp5rc")
=> false
>>


Well I created a workaround (load yaml from File, and Psych.load(yaml)) which gets me around that issue. Problem is majority of sketches now crash with an uncaught exception from the processing "Animation" thread (I've filed a bug report, but I am not holding my breath for a response, given my past experience).

Another example of a sketch using map1d in JRubyArt (development version of ruby-processing)

With every sketch that I look at with fresh eyes, the more convinced I am that ruby-processing does not need to (probably should not) ape processing convenience functions, and should where possible use idiomatic ruby. In this sketch we also use ruby array max method (built into ruby) in place of the processing convenience method:-
# Histogram. 
# 
# Calculates and displays the distribution of brightness levels 
# across an image, as a histogram. In this case the x scale is
# used for the brighness levels 0 .. 255, and the y scale is the
# frequency distribution. Note use of map1d function to map the
# ranges. Also for greater efficiency we load the image pixels.

attr_reader :hist

def setup
  size(640, 360)

  # Load an image from the data directory
  # Load a different image by modifying the comments
  img = load_image('frontier.jpg')
  image(img, 0, 0)
  img.load_pixels
  @hist = Array.new(256, 0)

  # Calculate the histogram
  (0 ... img.width).each do |i|
    (0 ... img.height).each do |j|
      bright = (brightness(img.pixels[j * img.width + i]))
      hist[bright] += 1
    end
  end

  # Find the largest value in the histogram using 'ruby' array max function
  hist_max = hist.max

  stroke(255)
  # Draw half of the histogram (skip every second value)
  (0 ... img.width).step(2) do |i|
    # Map i (from 0..img.width) to a location in the histogram (0..255)
    which = map1d(i, (0 .. img.width), (0 .. 255))
    # Convert the histogram value to a location between 
    # the bottom and the top of the picture
    y = map1d(hist[which], (0 .. hist_max), (img.height .. 0))
    line(i, img.height, i, y)
  end
end

Wednesday, 20 August 2014

A more ruby like constrain (for JRubyArt , ruby-processing development)

Having written a more ruby like version of the processing map method, I realized it might be better to have constrain also use a range input:-
# explicitly provide a ruby-processing constrain instance method
# to return a float:- amt, where range = (low .. high)
def constrain(amt, range)
  return amt if range.include? amt
  return range.first if amt < range.first
  return range.end
end

Here is a use case example, with excluded top value version of range
#
# A Processing implementation of Game of Life
# By Joan Soler-Adillon
#
# Press SPACE BAR to pause and change the cell's values with the mouse
# On pause, click to activate/deactivate cells
# Press R to randomly reset the cells' grid
# Press C to clear the cells' grid
#
# The original Game of Life was created by John Conway in 1970.
#

CELL_SIZE = 5
ALIVE = true
DEAD = false
ALIVE_START = 150
INTERVAL = 100

attr_reader :pause, :cells, :row, :column, :last_time, :alive, :cells_buffer

def setup
  size(960, 640)
  @pause = false
  # Instantiate arrays 
  @row = width / CELL_SIZE
  @column = height / CELL_SIZE
  @cells = Array.new(row) {Array.new(column) {(rand(1000) > ALIVE_START)? DEAD : ALIVE}}
  @last_time = 0
  @alive = color(100, 255, 100)
  # This stroke will draw the background grid (live cells)
  stroke(48, 100)
  noSmooth
end

def draw
  background(0)
  #Draw live cells
  row.times do |x|
    column.times do |y|
      if (cells[x][y])
        fill(alive)
        rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
      end
    end
  end
  # Iterate if timer ticks
  if (millis - last_time > INTERVAL)
    if (!pause)
      tick!
      @last_time = millis
    end
  end

  # Create  new cells manually on pause
  if (pause && mouse_pressed?)
    # # Map and avoid out of bound errors
    over_x = (map(mouse_x, 0, width, 0, row)).to_i
    over_x = constrain(over_x, (0 ... row))
    over_y = (map(mouse_y, 0, height, 0, column)).to_i
    over_y = constrain(over_y, (0 ... column))

    # Check against cells in buffer
    if (cells_buffer[over_x][over_y])  # Cell is alive
      cells[over_x][over_y] = DEAD # Kill
      fill(0) #reflect changed status
    else  # Cell is dead
      cells[over_x][over_y] = ALIVE # Make alive
      fill(alive) # Fill alive color
    end

  elsif (pause && !mouse_pressed?)  # And then save to buffer once mouse goes up
    # Save cells to buffer (so we operate with one array keeping the other intact)
    @cells_buffer = clone2d(cells)
  end
end

def tick!  # When the clock ticks
  # Save cells to buffer (so we operate with one array keeping the other intact)
  @cells_buffer = clone2d(cells)
  # Visit each cell:
  row.times do |x|
    column.times do |y|
      # And visit all the neighbours of each cell
      neighbours = 0 # We'll count the neighbours
      (x - 1 .. x + 1).each do |xx|
        (y - 1 .. y + 1).each do |yy|
          # Make sure you are not out of bounds
          if [(xx>=0), (xx<row), (yy>=0), (yy<column)].all? {|in_bounds| in_bounds == true}
            # Make sure to check against self
            if ![(xx == x), (yy == y)].all? {|is_self| is_self == true}
              if (cells_buffer[xx][yy]) # true == ALIVE
                neighbours += 1 # Check alive neighbours and count them
              end # alive
            end # End of if self
          end # End of if grid bounds
        end # End of yy loop
      end #End of xx loop
      # We've checked the neighbours: apply rules in one line (only in ruby)!
      cells[x][y] = (cells_buffer[x][y])?  ((2 .. 3) === neighbours) : (neighbours == 3)
    end # End of y loop
  end # End of x loop
end # End of function

def key_pressed
  case key
  when 'r', 'R'
    # Restart: reinitialization of cells    
    @cells = Array.new(row) {Array.new(column) {(rand(1000) > ALIVE_START)? DEAD : ALIVE}}
  when ' ' # On/off of pause
    @pause = !pause
  when 'c', 'C' # Clear all
    @cells = Array.new(row) {Array.new(column) {DEAD}}
  end
end

def clone2d array
  result = []
  array.each do |val|
    result << val.clone
  end
  return result
end

Here using included top value version:-
# Based on SeekingNeural example by Daniel Shiffman
# The Nature of Code
# http://natureofcode.com

load_library :vecmath


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| constrain(w + c, (0.0 .. 1.0)) }
      @weights = trained
    end

    # Give me a steering result
    def feedforward(forces)
      # Sum all values
      forces.zip(@weights).map { |a, b| a * b }.inject(Vec2D.new, :+)
    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 = constrain(location.x, (0 .. width))
      @location.y = constrain(location.y, (0 .. height))
    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

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

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
end

def mouse_pressed
  make_targets
end

Thursday, 14 August 2014

Experimental map1d method JRubyArt (ruby-processing development)

The recently updated JRubyArt has now removed the processing convenience functions that were deprecated in ruby-processing-2.5.1. Other functions such as map are retained, but I wondered would a map1d range method be a more ruby like alternative:-
# @param [float] val
# @param [range(start1 .. end1)] r_in
# @param [range(start2 .. end2)] r_out
# @return [float] mapped value
#
def map1d(val, r_in, r_out)
  r_out.first + (r_out.last - r_out.first) * ((val - r_in.first).to_f / (r_in.last - r_in.first))
end

# usage map1d(5, (0 .. 10), (10 .. 110))           exclude end is false

puts map1d(5, (0 .. 10), (10 .. 110))

# output 60.0

Here is the method in action
# Mandelbrot Set example
# by Jordan Scales (http://jordanscales.com)
# 27 Dec 2012
# Modified to use map1d (instead of map), and somewhat 
# optimized (update_pixels instead of set, and hypot for abs)

# default size 900x600
# no need to loop
def setup
  size 900, 600
  load_pixels
  no_loop
end

# main drawing method
def draw
  (0 ... 900).each do |x|
    (0 ... 600).each do |y|
      c = Complex.new(map1d(x, (0 .. 900), (-3 .. 1.5)), map1d(y, (0 .. 600), (-1.5 .. 1.5)))
      # mandel will return 0 to 20 (20 is strong)
      #   map this to 0, 255 (and flip it)
      pixels[x + y * 900] = color(255 - map1d(mandel(c, 20), (0 .. 20), (0 .. 255)).to_i)
    end
  end
  update_pixels
end

# calculates the "accuracy" of a given point in the mandelbrot set
#    : how many iterations the number survives without becoming chaotic
def mandel(z, max = 10)
  score = 0
  c = z.clone
  while score < max 
    # z = z^2 + c
    z.square
    z.add c
    break if z.abs > 2

    score += 1
  end

  score
end


# rolled my own Complex class
#   stripped of all functionality, except for what I needed (abs, square, add, to_s)
#   
#   Using this class, runs in ~12.5s on my MacBook Air
#     compared to ~22s using ruby's Complex struct
class Complex

  attr_accessor :real, :imag

  def initialize(real, imag)
    @real = real
    @imag = imag
  end

  # square a complex number - overwriting it
  def square
    r = real * real - imag * imag
    i = 2 * real * imag

    @real = r
    @imag = i
  end

  # add a given complex number
  def add(c)
    @real += c.real
    @imag += c.imag
  end

  # compute the magnitude
  def abs
    hypot(real, imag)
  end

  def to_s
    "#{real} + #{imag}i"
  end

end

PS: I have not tested whether Jordans findings re: ruby complex implementation still hold true (jruby truffle claims wicked fast mandelbrot calculations). Benchmarking is tricky with jvm warmup up time, I have replaced sqrt(real * real + imag * imag) (abs method) with hypot(real, imag), which should be more efficient...

Wednesday, 6 August 2014

Experimenting with a more explicit camera in ruby-processing

def setup
  size 400,400, P3D
  kamera(self,
    {
      x_eye: 200.0,
      y_eye: 200.0,
      z_eye: 800.0,
    }
   # kamera(self, 
    # {
      # x_eye: 200.0, 
      # y_eye: 200.0,
      # z_eye: 800.0, 
      # x_up: 0, 
      # y_up: 1, 
      # z_up: 0, 
      # x_look_at: 0, 
      # y_look_at: 0, 
      # z_look_at: 0
    # }
    )
  end

  def draw
    lights
    rotate_x PI / 4
    rotate_z PI / 6
    box(width / 2, height / 2, width)
  end

  def kamera(app, option = {})
    x_eye = option[:x_eye]
    y_eye = option[:y_eye]
    z_eye = option[:z_eye]
    x_up = option[:x_up] || 0
    y_up = option[:y_up] || 1
    z_up = option[:z_up] || 0
    x_at = option[:x_look_at] || 0
    y_at = option[:y_look_at] || 0
    z_at = option[:z_look_at] || 0
    app.camera(x_eye, y_eye, z_eye, x_at, y_at, z_at, x_up, y_up, z_up)
  end

Tuesday, 5 August 2014

Experimental post_initialization hook for ruby-processing

Sketch run from jEdit, two sketchs for price of one

Previously I experimented with classic initialization hook (that avoids need to call super) here is an experimental version of ruby-processing with a post initialization hook. Seems to work!!! here are the bits that enabled it in app.rb
def initialize(options={})
  super()
  post_initialize(options)
  $app = self
  proxy_java_fields
  #############
  # omitted code
  ############
end


def post_initialize(options)
  nil
end

Tuesday, 29 July 2014

Ruby-Processing-2.5.1 now available from rubygems.org

If you haven't installed ruby-processing before you will need:-
  1. a recent version of java (7+) 
  2. an installed version of vanilla processing-2.2.1
  3. a recent version of ruby (1.9.3+) preferably jruby-1.7.13 (but MRI ruby is OK)
  4. preferably wget (for downloading jruby-complete)
Recommended order of installation (skip 1 if you've installed ruby-processing recently)

  1. Set the processing root (you could run this sketch in processing ide)
  2. gem install ruby-processing (current version 2.5.1) 
  3. rp5 setup install (to download and install jruby-complete-1.7.13) 
  4. rp5 setup check (to check your setup) if you haven't got a system jruby installed you should set  JRUBY: 'false' in your ~/.rp5rc file 
  5. rp5 setup unpack_samples
  6. cd rp_samples 
  7. rake to to run contributed samples as a Demo
Choice of editor / ide

  • Believe it or not it is possible to use netbeans as a development environment for ruby
  • For a near to ide experience you could use jEdit
  • vim is perfect for console development (examples on this blog posted using syntax/2html.vim)
  • if you are a Mac you might use textmate

The simplest ArcBall sketch (under the hood sketch is translated to 300, 300 & arcball radius is set as 0.8 * height)
load_library :vecmath

############################
# Use mouse drag to rotate
# the arcball. Use mousewheel
# to zoom. Hold down x, y, z
# to constrain rotation axis.
############################

def setup
  size(600, 600, P3D)
  smooth(16)
  ArcBall.init(self, 300, 300)
  fill 180
end

def draw
  background(50)
  box(300, 300, 300)
end

See here for an example of DegLut table usage. Over 300 examples included with the gem, although there is no book for ruby-processing, code examples from Dan Shiffmans Learning Processing and his Nature of Code books have both been translated to work with ruby-processing here and here. If you are new to ruby or processing start with rp_samples/processing_app/basics, if you just want to be impressed, from the rp_samples directory run 'rake shaders', where you will see the level of performance that you might not have thought possible, given how slow ruby is supposed to be (running on gpu so it's a big cheat, but you can do this too see retained_menger.rb sketch).

Some On Line Tutorials (NB: all posted before ruby-processing-2.5.1, where install has changed)

  1. Introductory level (Jeff Casimir)
  2. An Introduction to Ruby Processing (Steve Kinney) 
  3. Drawing with processing and Ruby (Dhaivat Pandya 



Followers

About Me

My Photo
Consolidating my online identity as monkstone. I am a 64 bit linux user and advocate of open source software, you can sometimes find me on the processing forum.