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

Saturday, 27 April 2013

Hemesh mesh to VBO (PShape) in ruby-processing

Here is a sketch, pretty rich in ruby-processing syntax, including:-
  1. The building of a multi-dimensional array in ruby
  2. The casting of a multi-dimensional ruby array to a java array of float[][]
  3. Using "each" with a java iterator (it is so easy)
  4. Using bit shifting to create a color int
  5. Using the PShape vbo object
  6. A rubified do while block
The sketch:-
load_libraries :hemesh, :vbo

include_package 'wblut.math'
include_package 'wblut.processing'
include_package 'wblut.core'
include_package 'wblut.hemesh'
include_package 'wblut.geom'

RES = 20

attr_reader :mesh_ret, :inv_mesh_ret, :render

def setup
  size(800, 800, P3D)
  smooth(8)
  values = []               # build a multi-dimensional array in ruby
  (0 .. RES).each do |i|    # the inclusive range is intentional here
    valu = []
    (0 .. RES).each do |j|
      val = []
      (0 .. RES).each do |k|
        val << 2.1 * noise(0.35 * i, 0.35 * j, 0.35 * k)
      end
      valu << val
    end
    values << valu
  end

  creator = HEC_IsoSurface.new
  creator.set_resolution(RES,RES, RES) # number of cells in x,y,z direction
  creator.set_size(400.0/RES, 400.0/RES, 400.0/RES) # cell size

  # JRuby requires a bit of help to determine correct 'java args', particulary with 
  # overloaded arrays args as seen below. Note we are saying we have an 'array' of  
  # 'float array' here, where the values can also be double[][][].

  creator.set_values(values.to_java(Java::float[][])) # the grid points

  creator.set_isolevel(1)   # isolevel to mesh
  creator.set_invert(false) # invert mesh
  creator.set_boundary(100) # value of isoFunction outside grid
  # use creator.clear_boundary to set boundary values to "no value".
  # A boundary value of "no value" results in an open mesh

  mesh = HE_Mesh.new(creator)
  # mesh.modify(HEM_Smooth.new.set_iterations(10).setAutoRescale(true))
  creator.set_invert(true)

  inv_mesh = HE_Mesh.new(creator)
  inv_mesh.modify(HEM_Smooth.new.set_iterations(10).set_auto_rescale(true))
  @render = MeshToVBO.new(self)
  no_stroke
  # no color args produces a default light grey fill
  @mesh_ret = render.meshToVBO(mesh, color(200, 0, 0))
  @inv_mesh_ret = render.meshToVBO(inv_mesh, color(0, 0, 200))
end

def draw
  background(120)
  lights
  define_lights
  translate(400, 400)
  rotate_y(mouse_x.to_f / width * TWO_PI)  # use TWO_PI until processing-2.0b9
  rotate_x(mouse_y.to_f / height * TWO_PI) # then we can use TAU
  shape(inv_mesh_ret)
  shape(mesh_ret)
end

def define_lights
  ambient(20, 20, 20)
  ambient_light(60, 60, 60)
  point_light(30, 30, 30, 0, 0, 0)
  directional_light(40, 40, 50, 1, 0, 0)
  spot_light(30, 30, 30, 0, 40, 200, 0, -0.5, 0.5, PI / 2, 2)
end

Here is the mesh to vbo library
# mesh_to_vbo.rb
module MS
  include_package 'java.util'
  include_package 'processing'
  include_package 'processing.core'
  include_package 'wblut.geom'
  include_package 'wblut.hemesh'
end

class MeshToVBO
  include Processing::Proxy
  include MS
  attr_reader :parent

  def initialize(parent)
    @parent = parent
  end

  def meshToVBO(mesh, col = nil)
    tri_mesh = mesh.get
    tri_mesh.triangulate
    retained = parent.create_shape
    retained.begin_shape(TRIANGLES)
    if col
      retained.fill(col)
    else # we will have a light grey color, created by bit shifting
      fcol = (255 >> 24) & 0xFF|(211 >> 16) & 0xFF|(211 >> 8) & 0xFF|211
      retained.fill(fcol)
    end
    retained.ambient(50)
    retained.specular(50)
    mesh.fItr.each do |face|  # call each on the hemesh mesh iterator
      he = face.getHalfedge
      begin      # this block is the ruby equivalent of do while
        vx = he.getVertex
        vn = vx.getVertexNormal
        retained.normal(vn.xf, vn.yf, vn.zf)
        retained.vertex(vx.xf, vx.yf, vx.zf)
        he = he.getNextInFace
      end while (he != face.getHalfedge)
    end
    retained.end_shape
    return retained
  end
end

Using latest hemesh library with latest ruby-processing

Here is an example of using another java processing library with ruby-processing:-
load_library :hemesh

include_package 'wblut.math'
include_package 'wblut.processing'
include_package 'wblut.core'
include_package 'wblut.hemesh'
include_package 'wblut.geom'

 RES=20

attr_reader :mesh, :inv_mesh, :render

def setup
  size(800, 800, P3D)
  smooth(8)
  values = []
  (0 .. RES).each do |i|
    valu = []
    (0 .. RES).each do |j|
      val = []
      (0 .. RES).each do |k|
        val << 2.1*noise(0.35*i, 0.35*j, 0.35*k)
      end
      valu << val
    end
    values << valu
  end

  creator=HEC_IsoSurface.new
  creator.set_resolution(RES,RES, RES)# number of cells in x,y,z direction
  creator.set_size(400.0/RES, 400.0/RES, 400.0/RES) # cell size
  creator.set_values(values.to_java(Java::float[][]))# values corresponding to the grid points
  # values can also be double[][][]
  creator.set_isolevel(1)# isolevel to mesh
  creator.set_invert(false)# invert mesh
  creator.set_boundary(100)# value of isoFunction outside grid
  # use creator.clearBoundary to rest boundary values to "no value".
  # A boundary value of "no value" results in an open mesh

  @mesh=HE_Mesh.new(creator)
  # mesh.modify(HEM_Smooth.new.set_iterations(10).setAutoRescale(true))
  creator.set_invert(true)

  @inv_mesh=HE_Mesh.new(creator)
  inv_mesh.modify(HEM_Smooth.new.set_iterations(10).set_auto_rescale(true))
  @render=WB_Render.new(self)
end

def draw
  background(120)
  lights
  translate(400, 400, 0)
  rotate_y(mouse_x.to_f/width*TWO_PI)  # use TWO_PI until processing-2.0b9
  rotate_x(mouse_y.to_f/height*TWO_PI) # then we can use TAU
  no_stroke
  fill(255,0,0)
  render.draw_faces(inv_mesh)
  stroke(0)
  render.draw_edges(mesh)
  stroke(255,0,0,80)
  render.draw_edges(inv_mesh)
end

Friday, 26 April 2013

Using ruby 1.9 hash syntax to add sketch options

Previously I posted that the preferred mode for ruby-processing should be "bare" sketches, it works, and is closer to vanilla-processing (processing.js, processing.py and pyprocessing). However it is still possible to write sketches in the original "class mode" with my updated ruby-processing. Here is the full_screen sketch updated for ruby 1.9 hash syntax, works for me.
Just as matter of interest I experimented with building the original ruby-processing with jruby-complete-1.7.3, for some reason only sketches using the default renderer work, I can't be bothered to work out why the 3D sketches fail, but is something to do with the signature of the size/and or render_mode functions...
# Description:
# This is a full-screen demo 
# Since processing-2.0 it is opengl

class MySketch < Processing::App

  def setup
    size @width, @height, P3D
    no_stroke
  end

  def draw
    lights
    background 0
    fill 120, 160, 220
    (width/100).times do |x|
      (height/100).times do |y|
        new_x, new_y = x * 100, y * 100
        push_matrix
        translate new_x + 50, new_y + 50
        rotate_y(((mouse_x.to_f + new_x) / width) * Math::PI)
        rotate_x(((mouse_y.to_f + new_y) / height) * Math::PI)
        box 90
        pop_matrix
      end
    end
  end

end

# pass opts as ruby-1.9 hash
MySketch.new full_screen: true, width: 1280, height: 1024

Thursday, 25 April 2013

Understanding the ruby-processing examples (since processing-2.0 and ruby 1.9)

I've been pushing out a lot of ruby-processing samples, and suddenly realized that I should flesh out the reasons why some of the code looks the way it does:

  1. First of all, and this is very important for anyone looking at the older ruby-processing examples, all ruby-processing sketches should now be "bare sketches" (ie similar to what you might do in the processing ide the user does not explicitly wrap the code in class). However as with regular processing the code does get wrapped in a class at run time, with regular processing the class name is taken from the folder name, with ruby-processing it becomes the sketch class. I see this a good move, because it can be done, but also you might find that an explicitly wrapped sketch may not run, you have been warned.
  2. As with regular processing the default renderer is JAVA2D, but a change for ruby-processing since processing-2.0 the renderer SHOULD be defined as the third argument in "size", and preferably this is the first line in the "setup" block as with regular processing.
  3. When loading libraries, it is not usually sufficient to "load_library" or load_libraries" this should be followed by either import or include. If you want to include a java package I have preferred "include_package" over "java_import" (this is kosher since the Processing::App is a module). It is sometimes sensible to create your own module to facilitate the import of packages (outside of sketch file) the module will then act as "namespace" you can use to your advantage.
    module JavaUtil
      include_package "java.util"
    end
    
    JavaUtil::Date.new
    
  4. Since processing-2.0, I have found it necessary to explicitly "setVisible" the control_panel, which otherwise seems to work as expected (can't say I'm a big fan of the processing.event introduced in processing-2.0, and I had anticipated conflicts, but I've seen none so far), and certainly the use of reflection in "registerMethod" is just plain EVIL.

Sunday, 21 April 2013

More Explorations in Calling The Processing Register Method

Here we explore using "java_method" as an alternative to the "java_send", which may be inefficient, and in this case we call it twice, once to "register" the "pre" method and once to register the "draw" method so some progress there.


Sketch:
# A simple demonstration of vanilla processing # 'reflection' methods 
# in ruby-processing see register_method.rb for the guts...

require_relative 'register_method'

def setup
  size 200, 200
  RegisterMethod.new self
  no_loop
end

def draw
  fill(0, 0, 200)
  ellipse(120, 120, 60, 60)
end

Class with methods to "register" with the processing sketch (applet whatever), note the register method requires a java class Object hence the need for the "become_java!" magic
require 'jruby/core_ext' # required to allow become_java!

# This class demonstrates how to use 'reflection' methods in ruby-processing
# NB: the class must become a java object to get registered. This is an
# advanced feature in vanilla processing, mainly used by libraries.
class RegisterMethod
  attr_reader :parent

  def initialize(parent)
    @parent = parent
    register = parent.java_method :registerMethod, [java.lang.String, java.lang.Object]
    register.call(:draw, self)
    register.call(:pre, self)
  end

  def pre
    puts 'before draw'
    parent.background(100)
  end

  def draw
    puts 'at begin draw...'
    parent.fill(200, 100)
    parent.ellipse(100, 100, 60, 60)
  end
  # putting become_java! here works OK
  become_java!
end

Friday, 19 April 2013

More cellular automata (spore2 from vanilla processing)


#
# Spore 2 
# by Mike Davis. 
# 
# A short program for alife experiments. Click in the window to restart. 
# Each cell is represented by a pixel on the display as well as an entry in
# the array 'cells'. Each cell has a run method, which performs actions
# based on the cell's surroundings.  Cells run one at a time (to avoid conflicts
# like wanting to move to the same space) and in random order. 
#
load_library :cell

attr_reader  :cells, :black, :colours, :spore1, :spore2, :spore3, :spore4

MAX_CELLS = 8000

# set lower for smoother animation, higher for faster simulation
RUNS_PER_LOOP = 10000

def setup
  size(640, 360)
  frame_rate(24)
  @black = color(0, 0, 0)
  @spore1 = color(128, 172, 255)
  @spore2 = color(64, 128, 255)
  @spore3 = color(255, 128, 172)
  @spore4 = color(255, 64, 128)
  @colours =[spore1, spore2, spore3, spore4]
  reset!
end

def reset!
  clear_screen
  seed
end

def seed
  @cells = []
  # Add cells at random places
  MAX_CELLS.times do
    cx = rand(0 ... width)
    cy = rand(0 ... height)
    if (getpix(cx, cy) == black)
      setpix(cx, cy, colours.sample)
      cells << Cell.new(self, cx, cy)
    end
  end
end

def draw
  # Run cells in random order
  RUNS_PER_LOOP.times do
    cells.sample.run
  end
end

def clear_screen
  background(0)
end

def setpix(x, y, c)
  while (x < 0)
    x += width
  end
  while (x > (width - 1))
    x -= width
  end
  while (y < 0)
    y += height
  end
  while (y > (height - 1))
    y -= height
  end
  set(x, y, c)
end

def getpix(x, y)
  while (x < 0)
    x += width
  end
  while (x > (width - 1))
    x -= width
  end
  while (y < 0)
    y += height
  end
  while (y > (height - 1))
    y -= height
  end
  get(x, y)
end

def mouse_pressed
  reset!
end

class Cell

  attr_reader :outer, :x, :y, :width, :height, :black
  attr_reader :spore1, :spore2, :spore3, :spore4

  def initialize(outer, xin = 0, yin = 0)
    @outer, @x, @y = outer, xin, yin
    @width, @height, @black = outer.width, outer.height, outer.black
    @spore1, @spore2, @spore3, @spore4 = *outer.colours
  end

  # Perform action based on surroundings
  def run
    # Fix cell coordinates
    while (x < 0)
      @x += width
    end
    while (x > (width - 1))
      @x -= width
    end
    while (y < 0)
      @y += height
    end
    while (y > (height - 1))
      @y -= height
    end
    # Cell instructions
    my_color = outer.getpix(x, y)
    if (my_color == spore1)
      if (outer.getpix(x - 1, y + 1) == black && outer.getpix(x + 1, y + 1) == black && outer.getpix(x, y + 1) == black)
        move(0, 1)
      elsif (outer.getpix(x - 1, y) == spore2 && outer.getpix(x - 1, y - 1) != black)
        move(0, -1)
      elsif (outer.getpix(x - 1, y) == spore2 && outer.getpix(x - 1, y - 1) == black)
        move(-1, -1)
      elsif (outer.getpix(x + 1, y) == spore1 && outer.getpix(x + 1, y - 1) != black)
        move(0, -1)
      elsif (outer.getpix(x + 1, y) == spore1 && outer.getpix(x + 1, y - 1) == black)
        move(1, -1)
      else
        move(rand(1 .. 2), 0)
      end
    elsif (my_color == spore2)
      if (outer.getpix(x - 1, y + 1) == black && outer.getpix(x + 1, y + 1) == black && outer.getpix(x, y + 1) == black)
        move(0, 1)
      elsif (outer.getpix(x + 1, y) == spore1 && outer.getpix(x + 1, y - 1) != black)
        move(0, -1)
      elsif (outer.getpix(x + 1, y) == spore1 && outer.getpix(x + 1, y - 1) == black)
        move(1, -1)
      elsif (outer.getpix(x - 1, y) == spore2 && outer.getpix(x - 1, y - 1) != black)
        move(0, -1)
      elsif (outer.getpix(x - 1, y) == spore2 && outer.getpix(x - 1, y - 1) == black)
        move(-1, -1)
      else
        move(rand(1 .. 2), 0)
      end
    elsif (my_color == spore3)
      if (outer.getpix(x - 1, y - 1) == black && outer.getpix(x + 1, y - 1) == black && outer.getpix(x, y - 1) == black)
        move(0, -1)
      elsif (outer.getpix(x - 1, y) == spore4 && outer.getpix(x - 1, y + 1) != black)
        move(0, 1)
      elsif (outer.getpix(x - 1, y) == spore4 && outer.getpix(x - 1, y + 1) == black)
        move(-1, 1)
      elsif (outer.getpix(x + 1, y) == spore3 && outer.getpix(x + 1, y + 1) != black)
        move(0, 1)
      elsif (outer.getpix(x + 1, y) == spore3 && outer.getpix(x + 1, y + 1) == black)
        move(1, 1)
      else
        move(rand(1 .. 2), 0)
      end
    elsif (my_color == spore4)
      if (outer.getpix(x - 1, y - 1) == black && outer.getpix(x + 1, y - 1) == black && outer.getpix(x, y - 1) == black)
        move(0, -1)
      elsif (outer.getpix(x + 1, y) == spore3 && outer.getpix(x + 1, y + 1) != black)
        move(0, 1)
      elsif (outer.getpix(x + 1, y) == spore3 && outer.getpix(x + 1, y + 1) == black)
        move(1, 1)
      elsif (outer.getpix(x - 1, y) == spore4 && outer.getpix(x - 1, y + 1) != black)
        move(0, 1)
      elsif (outer.getpix(x - 1, y) == spore4 && outer.getpix(x - 1, y + 1) == black)
        move(-1, 1)
      else
        move(rand(1 .. 2), 0)
      end
    end
  end

  # Will move the cell (dx, dy) units if that space is empty
  def move(dx, dy)
    if (outer.getpix(x + dx, y + dy) == black)
      outer.setpix(x + dx, y + dy, outer.getpix(x, y))
      outer.setpix(x, y, black)
      @x += dx
      @y += dy
    end
  end
end

Wednesday, 10 April 2013

A simple install of jruby on linux for individual users

You absolutely should not install jruby supplied by your distribution (well at least not if it is debian based, actually ArchLinux version is OK) it will be hopelessly out of date and you will probably have no control over java version used.
OK you don't want anything too complicated so why not just unzip the tar file in your home directory (or possibly in /opt). So now you have folder jruby-1.7.4 with a sub folder bin. So now all you need to add that bin to your PATH in your .bashrc, then source .bashrc to update. Now when you type 'which jruby' it should point to the ~/jruby-1.7.4/bin/jruby
Actually in practice it is probably more convenient to create a symbolic link to say /usr/local/bin/jruby. I found rvm to be a real pig and probably over complicated, but that or brew may be way to go on the Mac? Actually I'm warming the idea of creating my own jruby script, in which I can set the java version and export JAVA_HOME and JRUBY_HOME also could set 'global' jruby args such as invoke-dynamic, see below (it is unfortunate the way -J--XX command gets split over two lines here).
#!/usr/bin/env bash
export RUBYOPT=rubygems
export JAVA_HOME=/opt/jdk1.7.0_40
export JRUBY_HOME=/opt/jruby-1.7.4
${JRUBY_HOME}/bin/jruby -J-XX:CompileCommand=dontinline,org.jruby.runtime.invokedynamic.InvokeDynamicSupport::invocationFallback $@

For installing gems using jgem or jruby -S gem you migh want to take a look at this .bashrc file I use on Archlinux (for ubuntu gems are /var/lib/gems/etc)
https://gist.github.com/monkstone/7614877

Tuesday, 9 April 2013

Should ruby-processing use regular jruby rather than jruby-complete?

Recently I've been corresponding with Alex Dean, who has come up with an interesting problem using the 'fisica' library with the development fork of ruby-processing here is sketch that illustrates the problem:-
#
#  Buttons and bodies
#
#  by Ricard Marxer
#
#  This example shows how to create a blob.
#

load_library :fisica
include_package 'fisica'

CIRCLE_COUNT = 20
HOLE = 50
TOP_MARGIN = 50
BOTTOM_MARGIN = 300
SIDE_MARGIN = 100


attr_reader :world, :x_pos

def setup
  size(400, 400)
  smooth
  @x_pos = 0
  Fisica.init(self)
  @world = FWorld.new
  world.setGravity(0, -300)
  l = FPoly.new
  l.vertex(width/2-HOLE/2, 0)
  l.vertex(0, 0)
  l.vertex(0, height)
  l.vertex(0 + SIDE_MARGIN, height)
  l.vertex(0 + SIDE_MARGIN, height-BOTTOM_MARGIN)
  l.vertex(width/2-HOLE/2, TOP_MARGIN)
  l.set_static(true)
  l.set_fill(0)
  l.set_friction(0)
  world.add(l)

  r = FPoly.new
  r.vertex(width/2 + HOLE/2, 0)
  r.vertex(width, 0)
  r.vertex(width, height)
  r.vertex(width-SIDE_MARGIN, height)
  r.vertex(width-SIDE_MARGIN, height-BOTTOM_MARGIN)
  r.vertex(width/2 + HOLE/2, TOP_MARGIN)
  r.set_static(true)
  r.set_fill(0)
  r.set_friction(0)
  world.add(r)
end

def draw
  background(80, 120, 200)
  fill(0)
  if ((frame_count % 40) == 1)
    b = FBlob.new
    s = rand(30 .. 40)
    space = (width - SIDE_MARGIN * 2-s)
    @x_pos = (x_pos + rand(s .. space/2.0)) % space
    b.set_as_circle(SIDE_MARGIN + x_pos + s / 2, height - rand(100), s, 20)
    b.set_stroke(0)
    b.set_stroke_weight(2)
    b.set_fill(255)
    b.set_friction(0)
    world.add(b)
  end

  world.step
  world.draw
end


def key_pressed
  save_frame("screenshot.png")
end
His experience which I have confirmed is that the FPoly setFill and setStroke methods were not recognized using jruby-complete. Something persuaded him to try using the installed jruby and the methods were no longer missing (anyway that was my experience, I'm not quite sure he might have had java issues on the Mac). Here is the sketch running on my linux box with jdk8 (works with jdk1.7.0_17 as well). Using the jruby-complete (ruby-processing installed) version it was impossible to get the methods recognized even using the usual java_send (reflection techniques).

Update 12 April 2013 Since then Jashkenas has given the all-clear to remove jruby-complete, since it was only included for exporting Applets. So I've been checking out what would happen, and this is what I've found:-

  1. The majority of sketches run OK, except java_args.txt will need changing from java args to jruby args, and there is a special command which allows args such as increasing memory to work (by starting a new proc).
  2. Some sketches fail completely these are mainly Shader examples (where the display is written directly to screen?, I've no idea why this should be the case).
  3. I think there may be version control issues and export app would no longer work.
  4. Currently I think I will retain jruby-complete (for reasons 2 & 3) but possibly make external jruby default (see next post for experiments on installing jruby on linux).


Further update 26 April having studied the java code for the fisica library I am slightly surprised it works at at all, there is a protected abstract class (probably a mistake) who's methods gets called (although these methods are declared public, I believe they should strictly be seen as protected since the class is not public). I'm not wasting any more time with despite nice looking examples.

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