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

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 



Saturday 19 July 2014

Ruby-processing at version 2.5.0 on github

Can't wait to try it out? The latest version of ruby-processing is now available at github. It is a bit trickier to build than previous versions and requires an installed jruby, ruby-compiler (gem) see instructions here. Actually the build is really easy just do "rake" and you are done (but you need to set your procssing root and you might not have ruby-compiler installed?). The compiled jruby extensions offer an interesting way in which to extend ruby-processing to use java libraries (not necessarily restricted to processing libraries), but especially with libraries that rely on reflection in their interface. The best exemplar of this the ArcBall utility, that uses reflection under the hood (to register pre()) so we can implement ArcBall functionality in a ruby processing sketch in one line (updates are sent to sketch via pre()). Obvious candidates for such an approach is java JBox2D, so would not have to rely on Dan Schiffmans PBox2D (now Box2DProcessing) or Ricard Marxers fisica, which could build on ruby-processings Vec2D.

ArcBall sketch code:-
# 1. drag mouse to rotate arcball
# 2. hold down x, y, or z to constrain arcball rotation to that axis
#

load_library :vecmath

# initialize arcball in setup and you are done...........

  # either provide arc ball center (sketch is translated to center)

  ArcBall.init(self, width/2.0, height/2.0)

  # or alternative form, where camera is set to point at center

  ArcBall.init(self)

DegLut sketch code:-
# DegLut.sin() and DegLut.cos() values

load_library :fastmath
# draw minute markers for analogue clock
  (0..360).step(6) do |a|
    x = 100 + DegLut.cos(a) * 72
    y = 100 + DegLut.sin(a) * 72
    point x, y
  end

A complete example showing how to initialize AppRender, and use it to allow Vec2D to be written (by sketch authors) directly as PApplet vertices
#
# Morph. 
# 
# Changing one shape into another by interpolating
# vertices from one to another
#
load_library :vecmath

attr_reader :circle, :square, :morph, :state, :v1, :v2, :renderer

ALPHA = Math::PI / 4.0
OMEGA = TAU + ALPHA
THETA = Math::PI / 20.0

def setup
  size(640, 360)
  @circle = []
  @square = []
  @morph = []
  @state = false
  @renderer = AppRender.new(self)
  frame_rate(15)
  # Create a circle using vectors pointing from center
  (ALPHA .. OMEGA).step(THETA) do |angle|
    # Note we are not starting from 0 in order to match the
    # path of a circle.  
    circle << Vec2D.from_angle(angle) * 100
    # Let's fill out morph Array with blank Vec2Ds while we are at it
    morph << Vec2D.new
  end

  # A square is a bunch of vertices along straight lines
  # Top of square
  (-50 .. 50).step(10) do |x|
    square << Vec2D.new(x, -50)
  end
  # Right side
  (-50 .. 50).step(10) do |y|
    square << Vec2D.new(50, y)
  end
  # Bottom, NB: can't negative step ruby so use your loaf
  5.downto(-5) do |x|
    square << Vec2D.new(x * 10, 50)
  end
  # Left side
  5.downto(-5) do |y|
    square << Vec2D.new(-50, y * 10)
  end
end

def draw
  background(51)

  # We will keep how far the vertices are from their target
  @total_distance = 0

  # Look at each vertex
  circle.length.times do |i|
    # Are we lerping to the circle or square?
    v1 = (state)?  circle[i] : square[i]
    # Get the vertex we will draw
    v2 = morph[i]

    # Lerp to the target
    v2.lerp!(v1, 0.1)
    # Check how far we are from target
    @total_distance += v1.dist(v2)
  end

  # If all the vertices are close, switch shape
  if (@total_distance < 0.08)
    @state = !state
  end

  # Draw relative to center
  translate(width/2, height/2)
  stroke_weight(4)
  # Draw a polygon that makes up all the vertices
  begin_shape
  no_fill
  stroke(255)
  morph.each do |v|
    v.to_vertex(renderer)
  end
  end_shape(CLOSE)
end
New updated version since (26 July 2014), attempts to set processing root automatically if there is no ~/.rp5rc. JRUBY config option allows users to default to using jruby-complete without the --nojruby flag.

Wednesday 9 July 2014

Exploring PApplet with jruby

There is a lot of clever stuff that goes on in jruby, that allows you to treat java objects as if they were ruby. Recently I had been interested in refactoring the ruby-processings library loader, but that may be premature since changes seem to be afoot with the jruby class loader?

Anyway what is the current situation?
You can simply 'require a jar' to add the jar contents to your CLASSPATH, you can then start using the java classes as if they were ruby objects see PApplet example below:-
require 'java'
require '/home/tux/processing-2.2.1/core/library/core.jar'
app = Java::ProcessingCore::PApplet.new
puts "inspect: #{app.inspect}"
app.set_size(200, 200)
puts "\nto_s: #{app.to_s}"
# print the default renderer
puts "\nrenderer: #{app.sketch_renderer}"
# prints a list of methods including '_size'
puts "\nmethods:\n #{ app.methods.reject{ |method| /_size/ !~ method.to_s} }"


Output:-

inspect: #<Java::ProcessingCore::PApplet:0x4656c38e>

to_s: processing.core.PApplet[panel0,0,0,200x200,invalid,layout=java.awt.FlowLayout]

renderer: processing.core.PGraphicsJava2D

methods:
 [:text_size, :unregister_size, :register_size, :preferred_size_set?, :preferred_size=, :maximum_size=, :set_size, :is_minimum_size_set, :preferred_size, :set_preferred_size, :is_minimum_size_set?, :is_maximum_size_set?, :preferred_size_set, :set_maximum_size, :get_size, :minimum_size_set, :maximum_size_set?, :minimum_size=, :get_maximum_size, :get_minimum_size, :is_preferred_size_set?, :maximum_size, :minimum_size_set?, :maximum_size_set, :set_minimum_size, :is_preferred_size_set, :minimum_size, :get_preferred_size, :is_maximum_size_set]


Alternatively you can make use of the global CLASSPATH directly:-
require 'java'
$CLASSPATH << '/home/tux/processing-2.2.1/core/library/core.jar'
app = Java::ProcessingCore::PApplet.new
puts "inspect: #{app.inspect}"
app.set_size(200, 200)
puts "\nto_s: #{app.to_s}"
# print the default renderer
puts "\nrenderer: #{app.sketch_renderer}"
# prints a list of methods including '_size'
puts "\nmethods:\n #{ app.methods.reject{ |method| /_size/ !~ method.to_s} }"

There is yet another way to do it using classloader directly:-

require 'java'
F = java.io.File
class_loader = JRuby.runtime.jruby_class_loader
class_loader.add_url(F.new('/home/tux/processing-2.2.1/core/library/core.jar').to_url)
app = Java::ProcessingCore::PApplet.new
puts "inspect: #{app.inspect}"
app.set_size(200, 200)
puts "\nto_s: #{app.to_s}"
# print the default renderer
puts "\nrenderer: #{app.sketch_renderer}"
# prints a list of methods including '_size'
puts "\nmethods:\n #{ app.methods.reject{ |method| /_size/ !~ method.to_s} }"



In ruby-processing exported sketches, the jars are currently added to the java CLASSPATH (ie on the java side) this may not be necessary anymore (no Applets), the current export process can mess with nested libraries (on the plus side you only need specify a directory to include the contained jars).
java -cp ".:../lib/ruby/*:../lib/*:../lib/library/*" org.jruby.Main ../lib/ruby-processing/runners/run.rb drawolver.rb

Tuesday 8 July 2014

Pondering whether to hide implementation detail of Renderers

In a previous post I posted code using my new renderer interface, that exposed the user to the fact that the renderer object were java objects (partly because I thought it would be more transparent, but that would be lost on many users?). But I could easily hide it, by changing vecmath.rb as follows:-
require 'rvecmath'

Java::ProcessingVecmathArcball::ArcballLibrary.new.load(JRuby.runtime, false)
Java::ProcessingVecmathVec2::Vec2Library.new.load(JRuby.runtime, false)
Java::ProcessingVecmathVec3::Vec3Library.new.load(JRuby.runtime, false)

AppRender = Java::ProcessingVecmath::AppRender
ShapeRender = Java::ProcessingVecmath::ShapeRender

The wise could use renderer.inspect to get the following, but it might not be obvious that they should do.
"#<Java::ProcessingVecmath::ShapeRender:0x49c2ebcc>"

Actually it just occurred to me that inspect is a synonym for toString, that I should explicitly provide....
Well I learnt something new there it is not synonym after all, but chose to create a toString anyway.

Monday 7 July 2014

Development plans for ruby-processing

The next release of ruby-processing will likely be version 2.5.0 (current release 2.4.4 using jruby-1.7.12).  The reason why this will not be a minor release is that I intend to incorporate my pre-compiled vecmath and fastmath libraries as developed on JRubyArt. Both these libraries have been created as jruby extensions (they are written in java, but can be loaded into the jruby runtime as ruby). Apart from the possible efficiency gains of using pre-compiled code, the compiled version of vecmath greatly simplifies the use of my ArcBall utility (only one line of code is required to include ArcBall functionality in a sketch).  What I propose to do is to roll back ruby processing commits to the 2.4.4 release, and set that as the stable branch (for the foreseeable future). I see no compelling reasons to issue anymore 2.4 point releases (eg to bump to jruby-1.7.13, and there seems no likelihood of any further processing 2.0 releases, now development has switched up to version 3.0).




JRubyArt has been using the much heralded jruby-9000, but that will only be at a preview release this summer, see tweet:-



Drawolver featuring new libraries, degree precision look up tables, and to_vertex utility for Vec3D:-
# Drawolver: draw 2D & revolve 3D

# Example shows how to use the vecmath library, including AppRender utility.
# On the ruby side features the use each_cons, a possibly a rare use for this 
# ruby Enumerable method?
# 2010-03-22 - fjenett (somewhat revised by Martin Prout 2014-07-06)

load_library :vecmath, :fastmath

attr_reader :drawing_mode, :points, :rot_x, :rot_y, :vertices, :renderer

def setup
  size 1024, 768, P3D
  @renderer = Java::ProcessingVecmath::AppRender.new(self)
  frame_rate 30
  reset_scene
end

def draw
  background 0
  if (!drawing_mode)
    translate(width/2, height/2)
    rotate_x rot_x
    rotate_y rot_y
    @rot_x += 0.01
    @rot_y += 0.02
    translate(-width/2, -height/2)
  end
  no_fill
  stroke 255
  points.each_cons(2) { |ps, pe| line ps.x, ps.y, pe.x, pe.y}

  if (!drawing_mode)
    stroke 125
    fill 120
    lights
    ambient_light 120, 120, 120
    vertices.each_cons(2) do |r1, r2|
      begin_shape(TRIANGLE_STRIP)
      r1.zip(r2).each { |v1, v2| v1.to_vertex(renderer); v2.to_vertex(renderer)}
      end_shape
    end
  end
end

def reset_scene
  @drawing_mode = true
  @points = []
  @rot_x = 0.0
  @rot_y = 0.0
end

def mouse_pressed
  reset_scene
  points << Vec3D.new(mouse_x, mouse_y)
end

def mouse_dragged
  points << Vec3D.new(mouse_x, mouse_y)
end

def mouse_released
  points << Vec3D.new(mouse_x, mouse_y)
  recalculate_shape
end

def recalculate_shape
  @vertices = []
  points.each_cons(2) do |ps, pe|
    b = (points.last - points.first).normalize!
    a = ps - points.first
    dot_product = a.dot b
    b *= dot_product
    normal = points.first + b
    c = ps - normal
    vertices << []
    (0..360).step(12) do |ang|
      e = normal + c * DegLut.cos(ang)
      e.z = c.mag * DegLut.sin(ang)
      vertices.last << e
    end
  end
  @drawing_mode = false
end


Sketch demonstrating use of ShapeRender (ShapeRender and AppRender both implement the Render interface which is used by Vec3D (and Vec2D) to_vertex, to_vertex_uv and to_normal methods, see here for earlier version without using ShapeRender, NB: the FastMath of that version has not survived:
# Trefoil, by Andres Colubri
# A parametric surface is textured procedurally
# by drawing on an offscreen PGraphics surface.
#
# Features (Vec3D).to_normal(renderer) and (Vec3D).to_vertex_uv(renderer, u, v)
# see line 62 for inititialization of renderer where obj is an instance of PShape
# renderer = Java::ProcessingVecmath::ShapeRender.new(obj)

load_library :vecmath

attr_reader :pg, :trefoil

def setup
  size(1024, 768, P3D)

  texture_mode(NORMAL)
  noStroke

  # Creating offscreen surface for 3D rendering.
  @pg = create_graphics(32, 512, P3D)
  pg.begin_draw
  pg.background(0, 0)
  pg.noStroke
  pg.fill(255, 0, 0, 200)
  pg.end_draw

  # Saving trefoil surface into a PShape3D object
  @trefoil = create_trefoil(350, 60, 15, pg)
end

def draw
  background(0)

  pg.begin_draw
  pg.ellipse(rand(0.0 .. pg.width), rand(0.0 .. pg.height), 4, 4)
  pg.end_draw

  ambient(250, 250, 250)
  pointLight(255, 255, 255, 0, 0, 200)

  push_matrix
  translate(width/2, height/2, -200)
  rotate_x(frame_count * PI / 500)
  rotate_y(frame_count * PI / 500)
  shape(trefoil)
  pop_matrix
end

# Code to draw a trefoil knot surface, with normals and texture 
# coordinates. Makes of the Vec3D Render interface (uses ShapeRender here).
# Adapted from the parametric equations example by Philip Rideout:
# http://iphone-3d-programming.labs.oreilly.com/ch03.html

# This function draws a trefoil knot surface as a triangle mesh derived
# from its parametric equation.
def create_trefoil(s, ny, nx, tex)

  obj = create_shape()

  obj.begin_shape(TRIANGLES)
  obj.texture(tex)
  renderer = Java::ProcessingVecmath::ShapeRender.new(obj)
  (0 ... nx).each do |j|
    u0 = j.to_f / nx
    u1 = (j + 1).to_f / nx
    (0 ... ny).each do |i|
      v0 = i.to_f / ny
      v1 = (i + 1).to_f / ny

      p0 = eval_point(u0, v0)
      n0 = eval_normal(u0, v0)

      p1 = eval_point(u0, v1)
      n1 = eval_normal(u0, v1)

      p2 = eval_point(u1, v1)
      n2 = eval_normal(u1, v1)

      # Triangle p0-p1-p2      
      n0.to_normal(renderer)
      (p0 * s).to_vertex_uv(renderer, u0, v0)
      n1.to_normal(renderer)
      (p1 * s).to_vertex_uv(renderer, u0, v1)
      n2.to_normal(renderer)
      (p2 * s).to_vertex_uv(renderer, u1, v1)

      p1 = eval_point(u1, v0)
      n1 = eval_normal(u1, v0)

      # Triangle p0-p2-p1      
      n0.to_normal(renderer)
      (p0 * s).to_vertex_uv(renderer, u0, v0)
      n2.to_normal(renderer)
      (p2 * s).to_vertex_uv(renderer, u1, v1)
      n1.to_normal(renderer)
      (p1 * s).to_vertex_uv(renderer, u1, v0)
    end
  end
  obj.end_shape
  return obj
end

# Evaluates the surface normal corresponding to normalized 
# parameters (u, v)
def eval_normal(u, v)
  # Compute the tangents and their cross product.
  p = eval_point(u, v)
  tangU = eval_point(u + 0.01, v)
  tangV = eval_point(u, v + 0.01)
  tangU -= p
  tangV -= p
  tangV.cross(tangU).normalize! # it is easy to chain Vec3D operations
end

# Evaluates the surface point corresponding to normalized 
# parameters (u, v)
def eval_point(u, v)
  a = 0.5
  b = 0.3
  c = 0.5
  d = 0.1
  s = TAU * u
  t = (TAU * (1 - v)) * 2

  sint = sin(t)
  cost = cos(t)
  sint15 = sin(1.5 * t)
  cost15 = cos(1.5 * t)

  r = a + b * cost15
  x = r * cost
  y = r * sint
  z = c * sint15

  dv = Vec3D.new(
  -1.5 * b * sint15 * cost - y,
  -1.5 * b * sint15 * sint + x,
  1.5 * c * cost15)

  q = dv.normalize     # regular normalize creates a new Vec3D for us
  qvn = Vec3D.new(q.y, -q.x, 0).normalize!  # chained Vec3D operations

  ww = q.cross(qvn)

  coss = cos(s)
  sins = sin(s)

  Vec3D.new(
  x + d * (qvn.x * coss + ww.x * sins),
  y + d * (qvn.y * coss + ww.y * sins),
  z + d * ww.z * sins)
end

Sketch demonstrating new ArcBall utility, all that's needed to allow mousewheel zoom, and mouse drag to rotate the cube (this form uses camera 'under the hood' to centralize the cube in sketch viewpane, alternative form translates the sketch to provided coordinates):-
load_library :vecmath

attr_reader :my_cube

def setup
  size(600, 600, P3D)
  smooth(16)
  ArcBall.init(self)
  @my_cube = create_shape(BOX, 300, 300, 300)
  my_cube.set_fill(color(100, 10, 100))

end

def draw
  background(50, 50, 100)
  define_lights
  lights
  stroke(0)
  shape(my_cube)
end

def define_lights
  ambient(20, 20, 20)
  ambient_light(50, 50, 50)
  point_light(30, 30, 30, 200, -150, 0)
  directional_light(0, 30, 50, 1, 0, 0)
  spot_light(30, 30, 30, 0, 40, 200, 0, -0.5, -0.5, PI / 2, 2)
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