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

Sunday 31 August 2014

Getting started with ruby-processing for wizards (best for vim users, though other editors can be used)

You have to start somewhere and for brevity I am assuming you've installed ruby-processing-2.6.2 here:-
rp5 create my_sketch 200 200
rp5 watch my_sketch.rb


In a new terminal start up vim
vim my_sketch.rb

def setup
  size 200, 200
end

def draw

end

Edit the sketch to change its background

def setup
  size 200, 200
end

def draw
  background 0
end

On saving the sketch refreshes for you

Now add a colored ellipse

def setup
  size 200, 200
end

def draw
  background 0
  fill 200, 0, 0
  ellipse 100, 100, 120, 100
end

Save the sketch again and is update once more, how cool is that!!!

Now this all easiest done using vim (I mean you only need two consoles and you are cooking, but you could do the same with emacs, jEdit, eric, textmate to name but a few decent editors). With processing-2.2.1 can also be use with PDF, P2D and P3D modes "rp5 create my_sketch 200 200 p3d" works to create P3D sketches. Now as far as I know there is no book on ruby processing but there is Dan Shiffmans nature of code, and the examples have been translated to ruby-processing here or for the beginner there is his Learning Processing examples translated here. Then there is the 300+ examples included with ruby-processing.........
The Nature of code book is now available in Japanese.

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

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