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

Friday 28 December 2012

Bezier Patch as a VBO (PShape)

Hardly needs introduction here is an alternative version of the bezier patch sketch, features ArcBall (arcball library is built in to my fork of ruby-processing, which is required to run a sketch with processing-2.0 functionality such as PShape) to rotate the patch and uses PShape object to store the geometry as a vbo.
# bezier bez_patch By Marius Watz:
# http://www.openprocessing.org/sketch/57709
# Normal calculation added by Andres Colubri
# Direct port of sample code by Paul Bourke.
# Original code: http://paulbourke.net/geometry/bezier/
#
# hit "spacebar" to generate a new shape and save current
#


load_library :vecmath

NI=4
NJ=5
RESI=NI*10
RESJ=NJ*10

attr_accessor :outp, :inp, :normp, :auto_normals, :arcball, :bez_patch


def setup
  size(1024, 768, P3D)
  @arcball = ArcBall.new(width/2.0, height/2.0, min(width - 20, height - 20) * 0.5)
  @auto_normals = false
  build_geometry
  @bez_patch = build_shape
end

def draw
  background(255)
  translate(width/2,height/2)
  smooth(8)
  lights
  define_lights
  #scale(0.9)
  update
  shape(bez_patch)
end

#######
# use bez patch geometry to
# create a vbo object (PShape)
#######

def build_shape
    no_stroke
    bez = create_shape(QUAD_STRIP)
    bez.fill(192, 192, 192)
    bez.ambient(20, 20, 20)
    (0 ... RESI - 1).each do |i|
        (0 ... RESJ).each do |j|
            if (!auto_normals)
                bez.normal(*normp[i][j])
            end
            bez.vertex(*outp[i][j])
            bez.vertex(*outp[i+1][j])
        end
    end
    bez.end     # end has unfortunate ruby highlighting in jedit, not in vim though
    return bez
end

##########
# build geometry
# for bez patch
##########

def build_geometry
  @outp = []
  @normp = []
  @inp = []
  uitang = PVector.new
  ujtang = PVector.new

  (0 ... NI).each do |i|
    row = Array.new
    (0 ... NJ).each do |j|
      row << PVector.new(i, j, (rand * 6) - 3)
    end
    inp << row
  end

  (0 ... RESI).each do |i|
    mui = i.fdiv(RESI - 1)
    row = []
    row_n = []
    (0 ... RESJ).each do |j|
      muj = j.fdiv(RESJ - 1)
      vect = PVector.new
      uitang.set(0, 0, 0)
      ujtang.set(0, 0, 0)
      (0 ... NI).each do |ki|
        bi = bezier_blend(ki, mui, NI)
        dbi = d_bezier_blend(ki, mui, NI)
        (0 ... NJ).each do |kj|
          bj = bezier_blend(kj, muj, NJ)
          dbj = d_bezier_blend(kj, muj, NJ)
          vect.x += (inp[ki][kj].x * bi * bj)
          vect.y += (inp[ki][kj].y * bi * bj)
          vect.z += (inp[ki][kj].z * bi * bj)

          uitang.x += (inp[ki][kj].x * dbi * bj)
          uitang.y += (inp[ki][kj].y * dbi * bj)
          uitang.z += (inp[ki][kj].z * dbi * bj)

          ujtang.x += (inp[ki][kj].x * bi * dbj)
          ujtang.y += (inp[ki][kj].y * bi * dbj)
          ujtang.z += (inp[ki][kj].z * bi * dbj)
        end
      end
      vect.add(PVector.new(-NI/2,-NJ/2,0))
      vect.mult(100)
      row << vect.array()
      uitang.normalize
      ujtang.normalize
      row_n << uitang.cross(ujtang).array()
    end
    @outp << row
    @normp << row_n
  end
end

def bezier_blend( k, mu,  n)
    blend = 1.0
    nn = n
    kn = k
    nkn = n - k

    while (nn >= 1)
        blend *= nn
        nn -= 1
        if (kn > 1)
            blend = blend.fdiv(kn)
            kn -= 1
        end
        if (nkn > 1)
            blend = blend.fdiv(nkn)
            nkn -= 1
        end
    end
    blend *= pow(mu, k.to_f) if (k > 0)
    blend *= pow(1-mu, (n - k).to_f) if (n - k > 0)
    return(blend)
end

def d_bezier_blend( k, mu,  n)
  dblendf = 1.0

  nn = n
  kn = k
  nkn = n - k

  while (nn >= 1)
    dblendf *= nn
    nn -= 1
    if (kn > 1)
      dblendf  = dblendf.fdiv(kn)
      kn -= 1
    end
    if (nkn > 1)
      dblendf  = dblendf.fdiv(nkn)
      nkn -= 1
    end
  end

  fk = 1
  dk = 0
  fnk = 1
  dnk = 0
  if (k > 0)
    fk = pow(mu, k.to_f)
    dk = k * pow(mu, (k - 1).to_f)
  end
  if (n - k > 0)
    fnk = pow(1 - mu, (n - k).to_f)
    dnk = (k - n)*pow(1 - mu, (n - k - 1).to_f)
  end
  dblendf *= (dk * fnk + fk * dnk)

  return(dblendf)
end

######################
# ArcBall control
# and lighting + re-run
######################

def update
    theta, x, y, z = arcball.update
    rotate(theta, x, y, z)
end

def mouse_pressed
    arcball.mouse_pressed(mouse_x, mouse_y)
end

def mouse_dragged
    arcball.mouse_dragged(mouse_x, mouse_y)
end

def define_lights
    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

def key_pressed
    case(key)
    when ' '
        save_frame("bezPatch.png")
        build_geometry
        @bez_patch = build_shape
    when 'x'
        arcball.select_axis(X)
    when 'y'
        arcball.select_axis(Y)
    when 'z'
        arcball.select_axis(Z)
    end
end

def key_released
    arcball.select_axis(-1)
end
Updated 20 February 2014 (arcball library is now in vecmath).

Thursday 27 December 2012

Bezier Patch in ruby-processing

This is getting to be a habit? Another sketch by Marius Watz translated to ruby-processing. The main reason is that I've been working on updating ruby-processing to processing-2.0, and in the course of that I'm including some sketches included with the processing-2.0 distribution and this is just another. Both these sketches benefit from taking a different approach building arrays in ruby cf java. This one has benefited from bit more tuning, as there no real need for an arrays of PVector once the math has been done. Further it is so easy to just splat array to the vertex coordinates, don't you just love ruby.
# bezier patch By Marius Watz:
# http://www.openprocessing.org/sketch/57709
# Normal calculation added by Andres Colubri
# Direct port of sample code by Paul Bourke.
# Original code: http://paulbourke.net/geometry/bezier/
#
# hit "spacebar" to generate a new shape and save current
#

NI=4
NJ=5
RESI=NI*10
RESJ=NJ*10

attr_accessor :outp, :inp, :normp, :auto_normals


def setup
  size(1024, 768, P3D)
  @auto_normals = false
  build
end

def draw
  background(255)
  translate(width/2,height/2)
  smooth(8)
  lights
  #scale(0.9)
  rotate_y(map(mouse_x, 0, width, -PI, PI))
  rotate_x(map(mouse_y, 0, height, -PI, PI))
  no_stroke
  fill(192, 192, 192)
  ambient_light(100, 100, 100)
  ambient(40)
  specular(30)
  (0 ... RESI - 1).each do |i|
    beginShape(QUAD_STRIP)
    (0 ... RESJ).each do |j|
      if (!auto_normals)
        normal(*normp[i][j])
      end
      vertex(*outp[i][j])
      vertex(*outp[i+1][j])
    end
    endShape
  end
end

def keyPressed
    if (key == ' ')
        save_frame("bezPatch.png")
        build
    end
end

def build
  @outp = []
  @normp = []
  @inp = []
  uitang = PVector.new
  ujtang = PVector.new

  (0 ... NI).each do |i|
    row = Array.new
    (0 ... NJ).each do |j|
      row << PVector.new(i, j, (rand * 6) - 3)
    end
    inp << row
  end

  (0 ... RESI).each do |i|
    mui = i.fdiv(RESI - 1)
    row = []
    row_n = []
    (0 ... RESJ).each do |j|
      muj = j.fdiv(RESJ - 1)
      vect = PVector.new
      uitang.set(0, 0, 0)
      ujtang.set(0, 0, 0)
      (0 ... NI).each do |ki|
        bi = bezier_blend(ki, mui, NI)
        dbi = d_bezier_blend(ki, mui, NI)
        (0 ... NJ).each do |kj|
          bj = bezier_blend(kj, muj, NJ)
          dbj = d_bezier_blend(kj, muj, NJ)
          vect.x += (inp[ki][kj].x * bi * bj)
          vect.y += (inp[ki][kj].y * bi * bj)
          vect.z += (inp[ki][kj].z * bi * bj)

          uitang.x += (inp[ki][kj].x * dbi * bj)
          uitang.y += (inp[ki][kj].y * dbi * bj)
          uitang.z += (inp[ki][kj].z * dbi * bj)

          ujtang.x += (inp[ki][kj].x * bi * dbj)
          ujtang.y += (inp[ki][kj].y * bi * dbj)
          ujtang.z += (inp[ki][kj].z * bi * dbj)
        end
      end
      vect.add(PVector.new(-NI/2,-NJ/2,0))
      vect.mult(100)
      row << vect.array()
      uitang.normalize
      ujtang.normalize
      row_n << uitang.cross(ujtang).array()
    end
    @outp << row
    @normp << row_n
  end
end

def bezier_blend( k, mu,  n)
    blend = 1.0
    nn = n
    kn = k
    nkn = n - k

    while (nn >= 1)
        blend *= nn
        nn -= 1
        if (kn > 1)
            blend = blend.fdiv(kn)
            kn -= 1
        end
        if (nkn > 1)
            blend = blend.fdiv(nkn)
            nkn -= 1
        end
    end
    blend *= pow(mu, k.to_f) if (k > 0)
    blend *= pow(1-mu, (n - k).to_f) if (n - k > 0)
    return(blend)
end

def d_bezier_blend( k, mu,  n)
  dblendf = 1.0

  nn = n
  kn = k
  nkn = n - k

  while (nn >= 1)
    dblendf *= nn
    nn -= 1
    if (kn > 1)
      dblendf  = dblendf.fdiv(kn)
      kn -= 1
    end
    if (nkn > 1)
      dblendf  = dblendf.fdiv(nkn)
      nkn -= 1
    end
  end

  fk = 1
  dk = 0
  fnk = 1
  dnk = 0
  if (k > 0)
    fk = pow(mu, k.to_f)
    dk = k * pow(mu, (k - 1).to_f)
  end
  if (n - k > 0)
    fnk = pow(1 - mu, (n - k).to_f)
    dnk = (k - n)*pow(1 - mu, (n - k - 1).to_f)
  end
  dblendf *= (dk * fnk + fk * dnk)

  return(dblendf)
end

Monday 24 December 2012

Rotating Arcs Sketch in Ruby Processing

Here is vanilla sketch that wasn't easy to translate to ruby, the more hacks there are, the harder it is to translate. However conversely it should make me a bit bit more sensitive to the same issue in reverse. The original sketch featured sincos look up tables which are a dubious benefit in java, especially when not optimised. It also featured a nasty ++ increment hack. The original sketch had quite lot of casting to int, I found there no casting required in ruby-processing, just a nifty built in deg to radian conversion using a patched Numeric class.
# 
#  Geometry 
#  by Marius Watz. 
#  
load_library 'pdf'
include_package 'processing.pdf'

attr_reader :num, :pt, :style, :dosave

def setup
  size(1024, 768, P3D)
  background(255)
  @dosave = false
  @num = 150
  @pt = []
  @style = []

  # Set up arc shapes
  index = 0
  (0 ... num).each do |i|
    pt.push(rand(TWO_PI)) # Random X axis rotation
    pt.push(rand(TWO_PI)) # Random Y axis rotation

    pt.push(rand(60 .. 80)) # Short to quarter-circle arcs
    if (rand(100)>90)
      pt[pt.length - 1] = rand(8 .. 27) * 10
    end
    pt.push(rand(2 .. 50) * 5) # Radius. Space them out nicely

    pt.push(rand(4 .. 32)) # Width of band
    if (rand(100) > 90)
      pt[pt.length - 1] = rand(40 .. 60) # Width of band
    end

    pt.push(rand(0.005 .. 0.0334))# Speed of rotation

    # get colors
    prob = rand(100)
    if (prob < 30)
      style[i*2] = color_blended(rand, 255,0,100, 255,0,0, 210)
    elsif(prob < 70)
      style[i*2] = color_blended(rand, 0,153,255, 170,225,255, 210)
    elsif(prob<90)
      style[i*2] = color_blended(rand, 200,255,0, 150,255,0, 210)
    else
      style[i*2] = color(255,255,255, 220)
    end

    if (prob < 50)
      style[i*2] = color_blended(rand, 200,255,0, 50,120,0, 210)
    elsif(prob < 90)
      style[i*2] = color_blended(rand, 255,100,0, 255,255,0, 210)
    else
      style[i*2] = color(255, 255, 255, 220)
      style[i*2+1] = rand(100) % 3
    end
  end
end

def draw
  if(dosave)
    # set up PGraphicsPDF for use with beginRaw
    pdf = begin_raw(PDF, "pdf_complex_out.pdf")
    # set default Illustrator stroke styles and paint background rect.
    pdf.stroke_join(MITER)
    pdf.stroke_cap(SQUARE)
    pdf.fill(0)
    pdf.no_stroke
    pdf.rect(0,0, width,height)
  end
  background(0)
  index = 0
  translate(width/2, height/2, 0)
  rotate_x(PI/6)
  rotate_y(PI/6)
  (0 ... num).each do |i|
    push_matrix
    rotate_x(pt[index])
    rotate_y(pt[index + 1])
    index += 2
    if (style[i*2+1] == 0)
      stroke(style[i*2])
      no_fill
      stroke_weight(1)
      arc_line(0,0, pt[index],pt[index + 1],pt[index + 2])
      index += 3
    elsif (style[i*2+1] == 1)
      fill(style[i*2])
      no_stroke
      arc_line_bars(0,0, pt[index],pt[index + 1],pt[index + 2])
      index += 3
    else
      fill(style[i*2])
      no_stroke
      arc(0,0, pt[index],pt[index + 1],pt[index + 2])
      index += 3
    end
    # increase rotation
    pt[index-5] += pt[index] / 10
    pt[index-4] += pt[index] / 20
    index += 1
    pop_matrix
  end
  if (dosave)
    end_raw
    @dosave=false
  end
end


# Get blend of two colors
def color_blended(fract, r, g, b, r2, g2, b2, a)
  r2 = (r2 - r)
  g2 = (g2 - g)
  b2 = (b2 - b)
  return color(r + r2 * fract, g + g2 * fract, b + b2 * fract, a)
end


# Draw arc line
def arc_line(x, y, deg, rad, w)
  a=(deg < 360)? deg : 0
  numlines = w/2
  (0 ... numlines).each do
    begin_shape
    (0 ... a).each do |i|
      vertex(cos(i.radians)*rad+x,sin(i.radians)*rad+y)
    end
    end_shape
    rad += 2
  end
end

# Draw arc line with bars
def arc_line_bars(x, y, deg, rad, w)
  a=(deg < 360)? deg / 4 : 0
  begin_shape(QUADS)
  (0 ... a).step(4) do |i|
    vertex(cos(i.radians)*(rad)+x,sin(i.radians)*(rad)+y)
    vertex(cos(i.radians)*(rad+w)+x,sin(i.radians)*(rad+w)+y)
    vertex(cos((i + 2).radians)*(rad+w)+x,sin((i + 2).radians)*(rad+w)+y)
    vertex(cos((i + 2).radians)*(rad)+x,sin((i + 2).radians)*(rad)+y)
  end
  end_shape
end

# Draw solid arc
def arc(x,y,deg,rad,w)
  a = (deg < 360)? deg : 0
  begin_shape(QUAD_STRIP)
  (0 ... a).each do |i|
    vertex(cos(i.radians)*(rad)+x,sin(i.radians)*(rad)+y)
    vertex(cos(i.radians)*(rad+w)+x,sin(i.radians)*(rad+w)+y)
  end
  end_shape
end

def mouse_pressed
  @dosave = true
end
# 
#  Geometry 
#  by Marius Watz. 
#  
load_library 'pdf'
include_package 'processing.pdf'

attr_reader :num, :pt, :style, :dosave

def setup
  size(1024, 768, P3D)
  background(255)
  @dosave = false
  @num = 150
  @pt = []
  @style = []

  # Set up arc shapes
  index = 0
  (0 ... num).each do |i|
    pt.push(rand(TWO_PI)) # Random X axis rotation
    pt.push(rand(TWO_PI)) # Random Y axis rotation

    pt.push(rand(60 .. 80)) # Short to quarter-circle arcs
    if (rand(100)>90)
      pt[pt.length - 1] = rand(8 .. 27) * 10
    end
    pt.push(rand(2 .. 50) * 5) # Radius. Space them out nicely

    pt.push(rand(4 .. 32)) # Width of band
    if (rand(100) > 90)
      pt[pt.length - 1] = rand(40 .. 60) # Width of band
    end

    pt.push(radians(rand(5 .. 30)) / 5 )# Speed of rotation

    # get colors
    prob = rand(100)
    if (prob < 30)
      style[i*2] = color_blended(rand, 255,0,100, 255,0,0, 210)
    elsif(prob < 70)
      style[i*2] = color_blended(rand, 0,153,255, 170,225,255, 210)
    elsif(prob<90)
      style[i*2] = color_blended(rand, 200,255,0, 150,255,0, 210)
    else
      style[i*2] = color(255,255,255, 220)
    end

    if (prob < 50)
      style[i*2] = color_blended(rand, 200,255,0, 50,120,0, 210)
    elsif(prob < 90)
      style[i*2] = color_blended(rand, 255,100,0, 255,255,0, 210)
    else
      style[i*2] = color(255, 255, 255, 220)
      style[i*2+1] = rand(100).to_i % 3
    end
  end
end

def draw
  if(dosave)
    # set up PGraphicsPDF for use with beginRaw
    pdf = begin_raw(PDF, "pdf_complex_out.pdf")
    # set default Illustrator stroke styles and paint background rect.
    pdf.stroke_join(MITER)
    pdf.stroke_cap(SQUARE)
    pdf.fill(0)
    pdf.no_stroke
    pdf.rect(0,0, width,height)
  end
  background(0)
  index = 0
  translate(width/2, height/2, 0)
  rotate_x(PI/6)
  rotate_y(PI/6)
  (0 ... num).each do |i|
    push_matrix
    rotate_x(pt[index])
    rotate_y(pt[index + 1])
    index += 2
    if (style[i*2+1] == 0)
      stroke(style[i*2])
      no_fill
      stroke_weight(1)
      arc_line(0,0, pt[index],pt[index + 1],pt[index + 2])
      index += 3
    elsif (style[i*2+1] == 1)
      fill(style[i*2])
      no_stroke
      arc_line_bars(0,0, pt[index],pt[index + 1],pt[index + 2])
      index += 3
    else
      fill(style[i*2])
      no_stroke
      arc(0,0, pt[index],pt[index + 1],pt[index + 2])
      index += 3
    end
    # increase rotation
    pt[index-5] += pt[index] / 10
    pt[index-4] += pt[index] / 20
    index += 1
    pop_matrix
  end
  if (dosave)
    end_raw
    @dosave=false
  end
end


# Get blend of two colors
def color_blended(fract, r, g, b, r2, g2, b2, a)
  r2 = (r2 - r)
  g2 = (g2 - g)
  b2 = (b2 - b)
  return color(r + r2 * fract, g + g2 * fract, b + b2 * fract, a)
end


# Draw arc line
def arc_line(x, y, deg, rad, w)
  a=(deg < 360)? deg : 0
  numlines = w/2
  (0 ... numlines).each do
    begin_shape
    (0 ... a).each do |i|
      vertex(cos(radians(i))*rad+x,sin(radians(i))*rad+y)
    end
    end_shape
    rad += 2
  end
end

# Draw arc line with bars
def arc_line_bars(x, y, deg, rad, w)
  a=(deg < 360)? deg / 4 : 0
  begin_shape(QUADS)
  (0 ... a).step(4) do |i|
    vertex(cos(radians(i))*(rad)+x,sin(radians(i))*(rad)+y)
    vertex(cos(radians(i))*(rad+w)+x,sin(radians(i))*(rad+w)+y)
    vertex(cos(radians(i + 2))*(rad+w)+x,sin(radians(i + 2))*(rad+w)+y)
    vertex(cos(radians(i + 2))*(rad)+x,sin(radians(i + 2))*(rad)+y)
  end
  end_shape
end

# Draw solid arc
def arc(x,y,deg,rad,w)
  a = (deg < 360)? deg : 0
  begin_shape(QUAD_STRIP)
  (0 ... a).each do |i|
    vertex(cos(radians(i))*(rad)+x,sin(radians(i))*(rad)+y)
    vertex(cos(radians(i))*(rad+w)+x,sin(radians(i))*(rad+w)+y)
  end
  end_shape
end

def mouse_pressed
  @dosave = true
end

Friday 21 December 2012

Context free sketch in regular ruby processing

Sometimes it is not necessary to use the ruby-cf3 library. I was contemplating adding parametrized shape to cf3ruby, so I thought I would try a simple context free art sketch to experiment with. Turn out this pretty easy to implement in regular ruby-processing (now using ruby-1.9 syntax for random range):-
# Grapher is based on a context free art design
# by ColorMeImpressed (takes a bit of time to run)
# http://www.contextfreeart.org/gallery/view.php?id=2844
#

CMIN = -2.0 # Important to specify float else get random int from range?
CMAX = 2.0
FUZZ = 0.04
SZ = 5

def setup
  size 600, 600
  no_stroke
  color_mode(HSB, 1.0)
  background(0)
  frame_rate(4000)
end

def draw
  translate(width/2, height/2)
  dot(rand(-PI .. PI), rand(-PI .. PI), rand(CMIN .. CMAX)) unless frame_count > 200000
end

def dot(px, py, c)
  func = sin(px) + sin(py) + c
  # change function to change the graph eg.
  #func = sin(px) + tan(py) + c    
  #func = cos(px) + sin(py) + c
  if abs(func) <= FUZZ
    fill(((CMIN - c) / (CMIN - CMAX)), 1, 1)
    ellipse px * width / TWO_PI, py * height / TWO_PI, SZ, SZ
  else
    dot(rand(-PI .. PI), rand(-PI .. PI), rand(CMIN .. CMAX))
  end
end

Wednesday 19 December 2012

Exporting cf3ruby directly to 'large' pdf

With the processing pdf export library it is easy to export processing sketches to pdf. This is also true for ruby-processing sketches, including those using the cf3ruby library see example below:-
# levy.rb ruby-processing NB: :alpha is now implemented ruby-processing
require 'cf3', 'pdf'
include_package 'processing.pdf'

def setup_the_levy
  @levy = ContextFree.define do
    shape :start do
      levy brightness: 0.9
    end
    shape :levy do
      square alpha: 0.1
     split do
        levy  size: 1/Math.sqrt(2), rotation: -45, x: 0.5, brightness: 0.9
        rewind
        levy  size: 1/Math.sqrt(2), rotation: 45, x: 0.5, brightness: 0.9
     end
    end
  end
end


def setup
  size 2000, 2000, PDF, "levy.pdf"
  setup_the_levy
  smooth
  draw_it
end


def draw
  exit
end


def draw_it
  background 255
  @levy.render :start, size: 250,  stop_size: 2,
        start_x: width/4, start_y: height/2
end

Tuesday 18 December 2012

Context Free Tree in cf3ruby

#################################################################
# A non deterministic sketch run it until you get a result you like
# uncomment "srand 5" to get a more deterministic result. It looked
# pretty good on my linux box (however I'm not sure how universal the 
# random seeding is in jruby)
#################################################################

require 'cf3'

def setup_the_tree
  @tree = ContextFree.define do
    shape :trunk, 20 do                         # rule has a probability weighting of 20
      circle size: 0.25, brightness: 0.5     # giving an actual probability = 0.952381
      scraggle y: -0.1                      # the minus is require by the upside down coordinate system             
    end

    shape :trunk, 1 do                      # rule has a probability weighting of 1
      branch size: 0.7                      # giving an actual probability = 0.047619  
    end

    shape :trunk, 0.02 do                    # empty rule top stop early    
    end

    shape :branch do
      split do                               # split is like a branch, rewind returns original context
        trunk rotation: 10
      rewind
        trunk rotation: -10
      end
    end

    shape :scraggle do                       # without an explicit weighting    
      trunk rotation: 5                      # probability of each scraggle rule 
    end                                      # is 0.5

    shape :scraggle do
      trunk rotation: -5
    end
  end
end

def setup
  size 600, 600
  srand 5
  smooth
  setup_the_tree
  background 255         # NB color mode here is "RGB 255", within context free definition 
  draw_it                # the color mode is "HSB 1.0", supports :hue, :saturation, :brightness
  save_frame "/home/tux/tree4.png"
end

def draw_it
  @tree.render :trunk, start_x: width/2, start_y: height * 0.9, stop_size: height/150, size: height/15
end

New context_free.rb (cf3ruby)

Here is my "y" window sketch using my modified ruby-processing library (features CF3 syntax, ie shape replaces rule, also ruby 1.9 hash syntax).
# y.rb ruby-processing
require 'cf3'
Y_TOP = -1 / Math.sqrt(3)
Y_BOT = Math.sqrt(3) / 6

def setup_the_triangle
  @triangle = ContextFree.define do
    ########  
    shape :start do
    unit brightness: 1.0
    end

    shape :unit do
      triangle size: 1.0
      split do
        unit size: 0.5, x: 0, y: Y_TOP/2, brightness: 0.8
        rewind
        unit size: 0.5, x: -0.25, y: -Y_TOP/4, brightness: 0.8
        rewind
        unit size: 0.5, x: 0.25, y: -Y_TOP/4, brightness: 0.8
      end
    end
    ########
  end
end

def setup
  size 1024, 1024
  setup_the_triangle
  no_stroke
  color_mode RGB, 1
  smooth
  draw_it
  save_frame("y.png")
end


def draw
   # Do nothing.
end

def draw_it
  background 225, 225, 0
  @triangle.render :start, size: height, stop_size: 0.5,
  start_x: width/2, start_y: height * 0.6
end

Friday 14 December 2012

MeshToVBO conversion ruby-processing

Since processing-2.0 there is the possibility of converting 3D meshes to vertical buffer objects (vbo) as instances of PShape. Here I explore a toxiclibs example sketch that instead of using toxis gfx package to render the shapes converts the shapes to vbo using a ruby library (albeit with a Processing::Proxy mixin. Here is the library:-
############################################
# mesh_to_vbo.rb
# a ruby library to convert toxi.mesh object
# to vbo (PShape) written by Martin Prout
# make use of Processing::Proxy mixin
############################################
class MeshToVBO
    include Processing::Proxy

    attr_reader :parent

    def initialize(parent)
        @parent = parent
    end

    def meshToVBO(mesh, smth)
        retained = parent.createShape(PShape::TRIANGLES)
        retained.enableStyle()
        retained.fill(222, 222, 222)
        retained.ambient(50)
        retained.shininess(10)
        retained.specular(50)
        if (smth)
            mesh.computeVertexNormals()
            mesh.getFaces.each do |f|
                retained.normal(f.a.normal.x, f.a.normal.y, f.a.normal.z)
                retained.vertex(f.a.x, f.a.y, f.a.z)
                retained.normal(f.b.normal.x, f.b.normal.y, f.b.normal.z)
                retained.vertex(f.b.x, f.b.y, f.b.z)
                retained.normal(f.c.normal.x, f.c.normal.y, f.c.normal.z)
                retained.vertex(f.c.x, f.c.y, f.c.z)
            end
        else
            mesh.getFaces.each do |f|
                retained.normal(f.normal.x, f.normal.y, f.normal.z)
                retained.vertex(f.a.x, f.a.y, f.a.z)
                retained.vertex(f.b.x, f.b.y, f.b.z)
                retained.vertex(f.c.x, f.c.y, f.c.z)
            end
        end
        retained.end()
        return retained
    end

    # variant
    # input array of meshes, output an array of shapes
    def meshToRetained(mesh, smth)
        rshapes = []
        (0 ... mesh.length).each do |i|
            rshapes.push(meshToVBO(mesh[i], smth))
        end
        return rshapes
    end
end



Here is the example sketch:-
# This example demonstrates how to rotate a number of meshes
# so that each points towards a common & user controlled focal point.
#
# Requires toxiclibs-0020 or newer
#
# (c) 2012 Karsten Schmidt / LGPL2 licensed
#
load_libraries 'toxiclibscore', 'vbo'

include_package 'toxi.geom'
include_package 'toxi.geom.mesh'

# container for mesh positions

attr_reader :positions, :vbo

def setup
  size(640,480,P3D)
  @vbo = MeshToVBO.new(self)
  @positions = []
  # compute mesh positions on circle in XZ plane
  (Circle.new(200).toPolygon2D(8)).each do |p|
    positions.push(p.to3DXZ)
  end
end

def draw
  background(51)
  lights
  no_stroke
  translate(width/2,height/2,0)
  rotate_x(-PI/6)
  # create manual focal point in XY plane
  focus = Vec3D.new((mouse_x - width/2), (mouse_y - height/2), 0)
  # create mesh prototype to draw at all generated positions
  # the mesh is a simple box placed at the world origin
  m = AABB.new(25).to_mesh
  # draw focus
  shape(vbo.meshToVBO(AABB.new(focus, 5).to_mesh, false))
  positions.each do |p|
    # align the positive z-axis of mesh to point at focus
    # mesh needs to be located at world origin for it to work correctly
    # only once rotated, move it to actual position
    shape(vbo.meshToVBO(m.copy.pointTowards(focus.sub(p), Vec3D::Z_AXIS).translate(p), false))
  end
  # draw connections from mesh centers to focal point
  stroke(0,255,255)
  positions.each do |p|
    line(p.x, p.y, p.z, focus.x, focus.y, focus.z)
  end
end

Wednesday 12 December 2012

Fix for ruby-processing context free

The adoption of jruby-1.7.0 for the latest version of processing has required changes to the context_free.rb script. See the change on my Pembrokeshire Branch. My dragon sketch and city sketches working fine, I haven't checked any other. Here is my dragon sketch updated to ruby-1.9 syntax (NB: continues work with 1.8 syntax)



# contributed by monkstone

load_library 'context_free'

def setup_the_dragon
  @dragon = ContextFree.define do

    rule :start do
      dragon alpha: 1
    end

    rule :dragon do
      square hue: 0, brightness: 0, saturation: 1, alpha: 0.02
      split do
        dragon size: 1/Math.sqrt(2), rotation: -45, x: 0.25, y: 0.25
        rewind
        dragon size: 1/Math.sqrt(2), rotation: 135, x: 0.25, y: 0.25
        rewind
      end
    end

  end
end

def setup
  size 800, 500
  setup_the_dragon
  smooth
  draw_it
end

def draw
  # Do nothing.
end

def draw_it
  background 255
  @dragon.render :start, size: width*0.8,  stop_size: 2,
                         start_x: width/3, start_y: height/3.5
end




















Here's a sketch with a simple custom shape of an isosceles triangle
load_library 'context_free'

def setup_the_spiral
  @spiral= ContextFree.define do
    ############ Begin defining custom terminal, an isoceles triangle     
    class << self
      define_method(:isoceles) do |some_options| # isoceles triangle
        size, options = *self.get_shape_values(some_options)
        rot = options[:rotation]
        rotate(rot) if rot
        $app.triangle(-0.5 * size, -0.5 * size, -0.5 * size, 0.5 * size, 0.5 * size, 0.5 * size)
        rotate(-rot) if rot
      end
    end
    ########### End definition of custom terminal 'isoceles'
    rule :spiral do
      isoceles brightness: -1, rotation: 90
      spiral rotation: 135, size: 1/sqrt(2), x: 1/sqrt(2)
    end
  end
end

def setup
  size 800, 500
  setup_the_spiral
  draw_it
end

def draw
  # Do nothing.
end

def draw_it
  background 255
  @spiral.render :spiral, size: height, start_x: width/3, start_y: height/2
end

Wednesday 5 December 2012

What works what does not and may not

I have just forked ruby-processing and updated my fork to match work in progress on my linux box to get ruby-processing running with the latest jruby-1.7.1 and latest processing-2.0b7 release. https://github.com/monkstone/ruby-processing clone it if you like. All samples should work, with the exception of the peasycam sketch (does work with an unofficially updated version of peasycam). Examples that rely on external "contributed" libraries probably will not work with the probable exception of toxiclibs (which is implementation agnostic). I don't think control panel likes working the latest version of processing (it was ok with processing-2.0b6).
Since web applets are no-longer supported with vanilla processing I don't think there's much call for it in ruby processing. For web presence with processing you are encouraged to write processing.js sketches (there is even preliminary support for coffeescript in processing ide). I have requested that my modified fork is pulled into core, which will make it easier to use but in the meantime you can clone it from github or fork it if you've a burning desire to implement something new.

Retained Shape (FBO) in Ruby Processing

Now we are cooking, this example demonstrated running ruby-processing compiled with a pre-released version of processing-2.0b7. Further it demonstrates the use of the FBO to vastly improve performance. All sketches run with this ruby-processing hack are very noisy (complaining about ambigous overloaded java methods eg background and fill).

BOX_SIZE = 20
MARGIN = BOX_SIZE * 2
DEPTH = 400
FINT = 3

attr_reader :box_fill, :grid, :fcount, :lastm, :frate

def setup
  size(640, 360, P3D)
  frame_rate(60)
  @fcount = 0
  @lastm = 0
  no_smooth()
  no_stroke()
  @grid = create_shape(GROUP)
  # Build grid using multiple translations 
  (-(DEPTH/2 + MARGIN) ... (DEPTH/2 - MARGIN)).step(BOX_SIZE) do |i|
    (-(height + MARGIN) ... (height - MARGIN)).step(BOX_SIZE) do |j|
      (-(width + MARGIN) ... (width - MARGIN)).step(BOX_SIZE) do |k|
        # Base fill color on counter values, abs function 
        # ensures values stay within legal range
        @box_fill = color(i.abs.to_i, j.abs.to_i, k.abs.to_i, 50)
        cube = create_shape(BOX, BOX_SIZE.to_f, BOX_SIZE.to_f, BOX_SIZE.to_f)
        cube.fill(box_fill)
        cube.translate(k, j, i)
        grid.add_child(cube)
      end
    end
  end
end

def draw
  background(255)
  hint(DISABLE_DEPTH_TEST)
  # Center and spin grid
  push_matrix()
  translate(width/2, height/2, -DEPTH)
  rotate_y(frame_count * 0.01)
  rotate_x(frame_count * 0.01)
  shape(grid)
  pop_matrix()
  hint(ENABLE_DEPTH_TEST)
  @fcount += 1
  m = millis()
  if (m - lastm > 1000 * FINT)
    @frate = fcount / FINT
    @fcount = 0
    @lastm = m
    puts("fps: #{frate}")
  end
  fill(0)
  text("fps: #{frate}", 10, 20)
end

Creating a Mock Vanilla Processing Distribution

It is possible to create a distribution version of processing from the git version. I wanted a mock version to use in my development version of ruby-processing.
These are the steps for a linux64 distribution:-
  1. Build the git version from the build folder
  2. cd linux/work
  3. mkdir processing-2.1.1
  4. mkdir processing-2.1.1/modes
  5. cp -rf modes/java processing-2.1.1/modes
  6. cp -rf core processing-2.1.1
  7. tar czvf processing-2.1.1-linux64.tgz processing-2.1.1
I do this so that I can test the latest development version of processing in ruby-processing.

Clone processing from github here:-

https://github.com/processing/processing
Updated 6 November 2013, to match github and most recent pre-version

Tuesday 4 December 2012

Landscape shader in ruby-processing

Feels like I'm on a roll, here is the processing-2.0 landscape shader example running with ruby-processing.
##
# Landscape
# Simple raymarching shader with camera, originally by Paulo Falcão
# Ported from the webGL version in GLSL Sandbox:
# http://glsl.heroku.com/e//3213.0
#
##

attr_reader :landscape, :pg

def setup
  size(640, 360, P2D)

  # This effect can be too demanding on older GPUs, 
  # so we can render it on a smaller res offscreen surface
  # @pg = create_graphics(320, 180, P2D)
  @pg = create_graphics(640, 360, P2D)
  pg.no_stroke()
  @landscape = load_shader("landscape.glsl")
  landscape.set("resolution", width.to_f, height.to_f)
end

def draw
  landscape.set("time", millis() / 1000.0)
  landscape.set("mouse", mouse_x.to_f, height - mouse_y.to_f)

  # This kind of raymarching effects are entirely implemented in the
  # fragment shader, they only need a quad covering the entire view 
  # area so every pixel is pushed through the shader.  
  pg.begin_draw()
  pg.shader(landscape)
  pg.rect(0, 0, width, height)
  pg.end_draw()

  # Scaling up offscreen surface to cover entire screen (if reduced).
  image(pg, 0, 0, width, height)
end

Fish-eye shader revisited with ruby-processing and processing-2.0

Previously one of the most popular posts (700+ hits) on this blog was my glgraphics post with the fish eye shader. Andres has now incorporated some the shader stuff into processing-2.0, here is essentially the same sketch running in ruby-processing, built with processing-2.0 (which includes built in support for glsl shaders).
##
# Fish Eye
# 
# This fish-eye shader is useful for dome projection.
##

attr_reader :fisheye, :canvas, :use_fish_eye

def setup()
  size(640, 640, P3D)
  @canvas = create_graphics(width, height, P3D)
  @fisheye = load_shader("FishEye.glsl")
  @use_fish_eye = true
  fisheye.set("aperture", 180.0)
end

def draw()
  canvas.begin_draw()
  canvas.background(0)
  canvas.stroke(255, 0, 0)
  (0...width).step(10){ |i| canvas.line(i, 0, i, height)}
  (0...height).step(10){ |i| canvas.line(0, i, width, i)}
  canvas.lights()
  canvas.no_stroke()
  canvas.translate(mouse_x, mouse_y, 100)
  canvas.rotate_x(frameCount * 0.01)
  canvas.rotate_y(frameCount * 0.01)
  canvas.box(100)
  canvas.end_draw()
  shader(fisheye) unless !use_fish_eye
  image(canvas, 0, 0, width, height)
end

def mouse_pressed()
  if (use_fish_eye)
    @use_fish_eye = false
    resetShader()
  else
    @use_fish_eye = true
  end
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