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

Sunday, 25 May 2014

Somewhat optimised version of trefoil sketch

I knew there was something missing (previous post), I had fully intended to optimise the trefoil sketch, here is my effort, where some values are pre-calculated, and although it look slightly messy putting values directly into new Vec3D instance:-
# Trefoil, by Andres Colubri
# A parametric surface is textured procedurally
# by drawing on an offscreen PGraphics surface.
# somewhat optimised version for ruby-processing (development version)

load_libraries :vecmath, :fastmath

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.
# 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)

  (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.shape_normal(obj)
      pa = p0 * s
      pa.shape_vertex(obj, u0, v0)
      n1.shape_normal(obj)
      pb = p1 * s
      pb.shape_vertex(obj, u0, v1)
      n2.shape_normal(obj)
      pc = p2 * s
      pc.shape_vertex(obj, u1, v1)

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

      # Triangle p0-p2-p1      
      n0.shape_normal(obj)
      pa.shape_vertex(obj, u0, v0)
      n2.shape_normal(obj)
      pc.shape_vertex(obj, u1, v1)
      n1.shape_normal(obj)
      pb = p1 * s
      pb.shape_vertex(obj, 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 = TWO_PI * u
  t = (TWO_PI * (1 - v)) * 2

  sint = FastMath.sin(t)
  cost = FastMath.cos(t)
  sint15 = FastMath.sin(1.5 * t)
  cost15 = FastMath.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 = FastMath.cos(s)
  sins = FastMath.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

Saturday, 24 May 2014

Extending ruby-processing with built in jruby extensions

A jruby extension need not be a gem (and in some ways extension as gems can be limiting with ruby-processing, as they do not work too easily with jruby-complete) instead it can become a custom or built in library. In the development branch of ruby-processing (fastmath fork) I have created two built in libraries as jruby extensions:-
  1. vecmath, as direct replacement for PVector and incorporation arcball functionality (uses jafama under hood)
  2. fastmath, incorporating degree precision deg/cos lookup table and a wrapper for some jafama functions

For the vecmath I took the advantage of working with java to create arcball functionality using processing reflection calls. I also chose to allow simple Vec3D to vertex and Vec3D to normal (this has got to be most efficient way since it avoids unecessary java to ruby, ruby to java conversions something missing in vanilla processing). Here is a sketch (original by Andrés Colubri) that uses the FastMath sin and cos functions (from jafama) and Vec3D to vertex and Vec3D to normal conversions.
# Trefoil, by Andres Colubri
# A parametric surface is textured procedurally
# by drawing on an offscreen PGraphics surface.

load_libraries :vecmath, :fastmath

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.
# 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)

  (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.shape_normal(obj)
      pa = p0 * s
      pa.shape_vertex(obj, u0, v0)
      n1.shape_normal(obj)
      pb = p1 * s
      pb.shape_vertex(obj, u0, v1)
      n2.shape_normal(obj)
      pc = p2 * s
      pc.shape_vertex(obj, u1, v1)

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

      # Triangle p0-p2-p1      
      n0.shape_normal(obj)
      pa.shape_vertex(obj, u0, v0)
      n2.shape_normal(obj)
      pc.shape_vertex(obj, u1, v1)
      n1.shape_normal(obj)
      pb = p1 * s
      pb.shape_vertex(obj, 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

  normUV = tangV.cross(tangU)
  normUV.normalize!
  return normUV
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 = TWO_PI * u
  t = (TWO_PI * (1 - v)) * 2

  r = a + b * FastMath.cos(1.5 * t)
  x = r * FastMath.cos(t)
  y = r * FastMath.sin(t)
  z = c * FastMath.sin(1.5 * t)

  dv = Vec3D.new
  dv.x = -1.5 * b * FastMath.sin(1.5 * t) * FastMath.cos(t) - (a + b * FastMath.cos(1.5 * t)) * FastMath.sin(t)
  dv.y = -1.5 * b * FastMath.sin(1.5 * t) * FastMath.sin(t) + (a + b * FastMath.cos(1.5 * t)) * FastMath.cos(t)
  dv.z = 1.5 * c * FastMath.cos(1.5 * t)

  q = dv
  q.normalize!
  qvn = Vec3D.new(q.y, -q.x, 0)
  qvn.normalize!
  ww = q.cross(qvn)

  pt = Vec3D.new
  pt.x = x + d * (qvn.x * FastMath.cos(s) + ww.x * FastMath.sin(s))
  pt.y = y + d * (qvn.y * FastMath.cos(s) + ww.y * FastMath.sin(s))
  pt.z = z + d * ww.z * FastMath.sin(s)
  return pt
end



For the curious the current version of ruby-processings jruby extensions are available here.

Wednesday, 21 May 2014

Creating a jruby extension as a gem

What's an extension:-

Many MRI gems use extensions to wrap libraries that are written in C with a ruby wrapper. Examples include nokogiri which wraps libxml2 and libxslt, pg which is an interface to the PostgreSQL database and the mysql and mysql2 gems which provide an interface to the MySQL database. This above was copied from a guide to creating a gem with extensions. In jruby java alternatives are preferred.

A jruby extension can act as a wrapper for a java library

The Apache commons-math library is an example of such a library, here is gem I've created as a jruby extension.

Thursday, 15 May 2014

Lambda clock (featuring DegLut tables now only in JRubyArt, soon ruby-processing)

There exists this vanilla processing example, which is ideal for our degree sin/cos lookup tables (and a more functional approach in ruby processing using lambda). Basically if you can do the math then I think you should (rather than hide behind dubious vanilla processing map/normalize utility they are not helping you they are hindering your brain).
# The current time can be read with the second(), minute(), 
# and hour() functions. In this example, DegLut.sin() and DegLut.cos() values
# are used to set the position of the hands, perfect for degree precision Lookup Table.
load_library :fastmath

def setup
  size 200, 200
  stroke 255
  smooth 8
end

def draw
  background 0
  fill 80
  no_stroke
  # adj factor to map 0-60 to 0-360 (seconds/minutes) & 0-12 to 0-360 (hours)
  # since angles for DegLut.sin() and DegLut.cos() start at 3 o'clock we
  # subtract 90 degrees to make them start at the top.
  clock_x = ->(val, adj, length){DegLut.cos((val * adj).to_i - 90) * length + width / 2}
  clock_y = ->(val, adj, length){DegLut.sin((val * adj).to_i - 90) * length + height / 2}
  ellipse 100, 100, 160, 160
  stroke 220
  stroke_weight 6
  line( 100, 100, clock_x.call(hour % 12 + (minute / 60.0), 30, 50), clock_y.call(hour % 12 + (minute / 60.0), 30, 50) )
  stroke_weight 3
  line( 100, 100, clock_x.call(minute + (second / 60.0), 6, 60), clock_y.call(minute + (second / 60.0), 6, 60) )
  stroke 255, 0, 0
  stroke_weight 1
  line( 100, 100, clock_x.call(second, 6, 72), clock_y.call(second, 6, 72) )
  # Draw the minute ticks
  stroke_weight 2
  stroke 255
  (0..360).step(6) do |a|
    x = 100 + DegLut.cos(a) * 72
    y = 100 + DegLut.sin(a) * 72
    point x, y
  end
end

Tuesday, 13 May 2014

Latest processing update

Processing has been updated, quite a few fixes, tested on linux with ruby-processing and it seems to make sense to update your version of processing for use with ruby-processing, just make sure you update the processing root in .rp5rc, to ensure you are using the right version.

Saturday, 10 May 2014

Enhanced exception handling in ruby 2.1.0

This one was promoted by headius (Charles Nutter), so guaranteed to work with jruby-9000? Get access to the cause of the original exception, when re-thrown.
class ExceptionalClass
  def exceptional_method
    cause = nil
    begin
      raise "Boom!" # RuntimeError raised
    rescue => e
      raise StandardError, "Ka-pow!"
    end
  end
end

def setup
  size 500, 120
  background 50, 50, 200
  f = createFont("Arial", 16, true)
  text_font(f, 16)
  fill(255, 0, 0)
  begin
    ExceptionalClass.new.exceptional_method
  rescue Exception => e
    exception = "Caught Exception: #{e.message} [#{e.class}]"
    cause = "Caused by : #{e.cause.message} [#{e.cause.class}]"
    text(exception, 20, 30)
    fill(255, 255, 0)
    text(cause, 20, 60)
  end
end


Exploring ruby-2.1.2 syntax in ruby-processing

To run the following sketch you need to be using jruby-9000 (the development version of jruby which supports ruby 2.1.0 syntax out of the box), here I am actually used JRubyArt the development version of ruby-processing (that uses jruby-9000 by default). This sketch uses the very concise syntax for rationals in ruby-2.1.0 (fractions for the masses)
def setup
  size 640, 250
  background 10
  f = createFont("Arial", 24, true)
  third = 1 / 3r     # since ruby 2.1.0
  quarter = 1 / 4r
  add = "#{third} + #{quarter} = #{third + quarter}"
  subtract = "#{third} - #{quarter} = #{third - quarter}"
  multiply = "#{third} * #{quarter} = #{third * quarter}"
  text_font(f, 24)
  fill(220)
  text("Math blackboard ruby-processing", 80, 50)
  text(add, 110, 100)
  text(subtract, 110, 150)
  text(multiply, 110, 200)
end


 
Update 16th November, after a long hiatus the sketch is now working again with jruby 9000.dev-SNAPSHOT (2.1.2p142) 2014-11-08 8a13045 OpenJDK 64-Bit Server VM 24.65-b04 on 1.7.0_65-b32 +jit [linux-amd64]. Unfortunately not many others sketches will currently run...

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