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

Sunday 12 January 2014

Frame of Reference Example Sketch (Original by Ira Greenberg)

Here's another processing sketch that I've translated to ruby-processing, where I replace PVector with Vec3D from the ruby processing vecmath library. It also makes use of the ArcBall functionality built into the vecamth library.
###############
# Frame of Reference example by Ira Greenberg
# https://github.com/irajgreenberg/ProcessingTips
# Translated to ruby-processing by Martin Prout January 2014
# Now use mouse drag for ArcBall manipulation, and  +/- keys for zoom
###############

load_library :vecmath
load_library :geometry

FACE_COUNT = 50

attr_reader :arcball, :c, :p, :zoom

def setup
  size(800, 800, P3D)
  @zoom = 1.0
  camera(0, 0, (height/2.0) / tan(PI*30.0 / 180.0), 0, 0, 0, 0, -1, 0) # point camera at origin
  # create an Arcball at centre that pretty much fills the screen
  @arcball = ArcBall.new(0, 0, min(width - 20, height - 20) / 2.0)
  @c = []
  @p = []
  FACE_COUNT.times do |i|

    # calc some random triangles in 3 space
    val = Vec3D.new(rand(-width/2 .. width/2), rand(-width/2 .. width/2), rand(-width/2 .. width/2))
    v0 = Vec3D.new(rand(-val.x .. -val.x + 100), rand(-val.y .. -val.y + 100), rand(-val.z .. -val.z + 100))
    v1 = Vec3D.new(rand(-val.x .. -val.x + 100), rand(-val.y .. -val.y + 100), rand(-val.z .. -val.z + 100))
    v2 = Vec3D.new(rand(-val.x .. -val.x + 100), rand(-val.y .. -val.y + 100), rand(-val.z .. -val.z + 100))
    p << Plane.new([v0, v1, v2])

    # build some cute little cylinders
    c << Cylinder.new(Vec3D.new(150, 5, 5), 12)

    # Using each Triangle normal (N), 
    # One of the Triangle's edges as a tangent (T)  
    # Calculate a bi-normal (B) using the cross-product between each N and T
    # Note caps represent constants in ruby so we used N = nn, T = tt and B = bb in the ruby code below

    #
    # A picture helps
    # nice, sweet orthogonal axes 

    # N   B
    # |  /
    # | /
    # |/____T

    #
    # N, T, B together give you a Frame of Reference (cute little local coordinate system), based on each triangle. 
    # You can then take the cylinder (or any vertices) and transform them using a 3 x 3 matrix to this coordinate system.
    # (In the matrix each column is based on N, T and B respectivley.) 
    # The transform will handle any rotations and scaling, but not the translation, 
    # but we can add another dimenson to the matrix to hold the translation values.  
    # Here's what all this confusing description looks like:

    #
    # Matrix :                               Vector :
    # |  N.x  T.x  B.x  translation.x  |      |  x  |
    # |  N.y  T.y  B.y  translation.y  |      |  y  |
    # |  N.z  T.z  B.z  translation.z  |      |  z  |
    # |  0    0    0    1              |      |  1  |

    # We add the extra row in the matrix and the 1 to each vector 
    # so the math works. We describe the Matrix as 4 rows by 4 columns
    # and the vector now as a Matrix with 4 rows and 1 column. 
    # When you multiply matrices the inner numbers MUST match, so: 
    # [4 x 4] [4 x 1] is OK, but [4 x 4] [1 x 4] is NOT COOL.

    # (Please note there is also row vector approach that you can use,
    # Google about; it simply puts the vector on left side of matrix and treats
    # it as a 1 row and 4 column matrix. However, you'll also need to shift
    # the translation terms to the bottom of the matrix for the math to grock.)

    # The Matrix multiplication looks like this (sorry it's a little tedious looking.)
    # n.x * x + t.x *y + B.x * z + translation.x * 1  =  new transformed x
    # n.y * x + t.y *y + B.y * z + translation.y * 1  =  new transformed y
    # n.z * x + t.z *y + B.z * z + translation.z * 1  =  new transformed z
    # 0 * x + 0 *y + 0 * z + 1 * 1   =   disregard this crap.
    #

    nn = p[i].n
    tt = Vec3D.new(p[i].vecs[1].x - p[i].vecs[0].x, p[i].vecs[1].y - p[i].vecs[0].y, p[i].vecs[1].z - p[i].vecs[0].z)
    nn.normalize!
    tt.normalize!
    bb = nn.cross(tt)
    bb.normalize! # not really needed

    # build matrix with frame and translation (to centroid of each triangle)
    m4 = Mat4.new(nn, tt, bb, p[i].c)

    # transform each cylinder to align with each triangle
    c[i].vecs = m4.mult(c[i].vecs)
  end
  fill(187)
  stroke(50, 20)
end

def draw
  background(0)
  lights
  # update the arcball rotation
  update
  FACE_COUNT.times do |i|
    p[i].display
    c[i].display
  end
end

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 key_pressed
  case key
  when '+'
    @zoom -= 0.1  # closer is bigger
  when '-'
    @zoom += 0.1
  end
  camera(0, 0, (height * zoom / 2.0) / tan(PI*30.0 / 180.0), 0, 0, 0, 0, -1, 0)
end


Here is the Plane class
NORM_LEN = 225.0

class Plane
  include Processing::Proxy

  attr_reader :vecs, :c, :n


  def initialize(vecs)
    @vecs = vecs
    init
  end

  def init
    v1 = vecs[1].dup
    v2 = vecs[2].dup
    v1 -= vecs[0]
    v2 -= vecs[0]

    @c = Vec3D.new(
      (vecs[0].x+vecs[1].x+vecs[2].x) / 3,
      (vecs[0].y+vecs[1].y+vecs[2].y) / 3,
      (vecs[0].z+vecs[1].z+vecs[2].z) / 3
      )

    @n = v1.cross(v2)
    n.normalize!
  end

  def display
    begin_shape(TRIANGLES)
    vecs.each do |vec|
      vertex(vec.x, vec.y, vec.z)
    end
    end_shape

    #normal
    stroke(200, 160, 30)
    begin_shape(LINES)
    vertex(c.x, c.y, c.z)
    vertex(c.x + n.x * NORM_LEN, c.y + n.y * NORM_LEN, c.z + n.z * NORM_LEN)
    end_shape

    #binormal
    stroke(160, 200, 30)
    begin_shape(LINES)
    vertex(c.x, c.y, c.z)
    # tangent
    v = vecs[1].dup
    #v.set(vecs[1])
    v -= vecs[0]
    v.normalize!
    vertex(c.x + v.x * NORM_LEN, c.y + v.y * NORM_LEN, c.z + v.z * NORM_LEN)
    end_shape

    stroke(30, 200, 160)
    begin_shape(LINES)
    vertex(c.x, c.y, c.z)
    b = v.cross(n)
    vertex(c.x + b.x * NORM_LEN, c.y + b.y * NORM_LEN, c.z + b.z * NORM_LEN)
    end_shape
    stroke(0, 75)
  end
end



Here is the Mat4 class
# uber simple Homogeneous 4 x 4 matrix

class Mat4
  attr_reader :mat

  def initialize(axisX, axisY, axisZ, trans)
    @mat = [
    [axisX.x, axisY.x, axisZ.x, trans.x],
    [axisX.y, axisY.y, axisZ.y, trans.y],
    [axisX.z, axisY.z, axisZ.z, trans.z],
    [0, 0, 0,  1]
    ]
  end

  # The processing version changes the input 'array', here we return
  # a new array with transformed values (which we then assign to the input)
  # see line 91 Frame_of_Reference.rb

  def mult(array)
    temp = []
    array.each do |arr|
      xt = mat[0][0] * arr.x + mat[0][1] * arr.y + mat[0][2] * arr.z + mat[0][3] * 1
      yt = mat[1][0] * arr.x + mat[1][1] * arr.y + mat[1][2] * arr.z + mat[1][3] * 1
      zt = mat[2][0] * arr.x + mat[2][1] * arr.y + mat[2][2] * arr.z + mat[2][3] * 1
      temp << Vec3D.new(xt, yt, zt)
    end
    return temp
  end
end


Here is the Cylinder class
class Cylinder
  include Processing::Proxy
  attr_accessor :vecs
  attr_reader :detail, :dim



  def initialize(dim, detail)
    @dim = dim
    @detail = detail
    init
  end

  def init
    theta = 0.0
    #    created around x-axis
    #    y = Math.cos
    #    z = Math.sin
    veca = []
    vecb = []
    detail.times do
      veca << Vec3D.new(0, Math.cos(theta)*dim.y, Math.sin(theta)*dim.z)
      vecb << Vec3D.new(dim.x, Math.cos(theta)*dim.y, Math.sin(theta)*dim.z)
      theta += Math::PI * 2/detail
    end
    @vecs = veca.concat(vecb)
  end

  def display
    begin_shape(QUADS)
    detail.times do |i|
      if (i<detail-1)
        vertex(vecs[i].x, vecs[i].y, vecs[i].z)
        vertex(vecs[i+1].x, vecs[i+1].y, vecs[i+1].z)
        vertex(vecs[detail+i+1].x, vecs[detail+i+1].y, vecs[detail+i+1].z)
        vertex(vecs[detail+i].x, vecs[detail+i].y, vecs[detail+i].z)
      else
        vertex(vecs[i].x, vecs[i].y, vecs[i].z)
        vertex(vecs[0].x, vecs[0].y, vecs[0].z)
        vertex(vecs[detail].x, vecs[detail].y, vecs[detail].z)
        vertex(vecs[detail+i].x, vecs[detail+i].y, vecs[detail+i].z)
      end
    end
    end_shape
  end
end

Thursday 9 January 2014

Penrose tiling generator (after Shiffman)

Previously I have experimented with cfdg and lsystems to generate penrose tiling, this version by Dan Shiffman seems very similar to the cfdg version. Interestingly Dan ported this somewhat from python, and here we end with a ruby version. Dan unsurprisingly makes use of the PVector class to do the vector math here we use my Vec2D from the ruby-processing vecmath library, which is far more concise (designed to use arithmetic operators from the outset). Here is the sketch, somewhat drier than the original with choosable options via control panel (it is necessary to press "reset" to change seed/triangle type):-
# Penrose Tile Generator
# Using a variant of the "ArrayList" recursion technique: http://natureofcode.com/book/chapter-8-fractals/chapter08_section4
# Penrose Algorithm from: http://preshing.com/20110831/penrose-tiling-explained
# Daniel Shiffman May 2013
# Translated (and refactored) to ruby-processing Jan 2014 by Martin Prout

load_libraries :vecmath, :tile, :control_panel
attr_reader :tris, :s, :panel, :hide, :acute

def setup
  size(1024, 576)
  control_panel do |c|
    c.title = "Tiler Control"
    c.look_feel "Nimbus"
    c.checkbox  :seed
    c.checkbox  :acute
    c.button    :generate
    c.button    :reset!
    @panel = c
  end
  @hide = false
  init false # defaults to regular penrose
end

def draw
  # only make control_panel visible once, or again when hide is false
  unless hide
    @hide = true
    panel.setVisible(hide)
  end
  background(255)
  translate(width/2, height/2)
  tris.each do |t|
    t.display
  end
end

def generate
  next_level = []
  tris.each do |t|
    more = t.subdivide
    more.each do |m|
      next_level << m
    end
  end
  @tris = next_level
end

def reset!
  Tiler.acute(acute)  # set the Tiler first
  init @seed
  java.lang.System.gc # but does it do any good?
end

def init alt_seed
  @tris = []
  10.times do |i|     # create 36 degree segments
    a = Vec2D.new
    b = Vec2D.from_angle((2 * i - 1) * PI / 10)
    c = Vec2D.from_angle((2 * i + 1) * PI / 10)
    b *= 370
    c *= 370
    if alt_seed
      tile = (i % 2 == 0)? Tiler.tile(b, a, c) : Tiler.tile(c, a, b)
      tris << tile
    else
      tile = (i % 2 == 0)? Tiler.tile(a, b, c) : Tiler.tile(a, c, b)
      tris << tile
    end
  end
end

Here is the tile library:-
module Tiler
  @@acute = false

  def self.acute(x)
    @@acute = x
  end

  # setup the initial tiling with all red tiles
  def self.tile(a, b, c)
    tile = (@@acute)? ATile.new(0, a, b, c) : Tile.new(0, a, b, c)
  end
end

class Tile
  include Processing::Proxy
  PHI = (1.0 + Math.sqrt(5)) / 2.0 # golden ratio
  RED = [255, 0, 0]
  BLUE = [0, 0, 255]
  COLORS = [RED, BLUE]
  attr_reader :a, :b, :c, :col

  def initialize(col,  a,  b,  c)
    @col, @a, @b, @c = col, a, b, c
  end

  def display
    no_stroke
    fill(*COLORS[col])
    triangle(a.x, a.y, b.x, b.y, c.x, c.y)
    #fill(0,0,255)
    #ellipse(a.x,a.y,4,4)
    #ellipse(b.x,b.y,4,4)
    #ellipse(c.x,c.y,4,4)
  end

  def subdivide
    result = []
    if (col == 0)
      # Subdivide red triangle
      p = b - a
      p /= PHI
      p += a
      result << Tile.new(0, c, p, b)
      result << Tile.new(1, p, c, a)
    else
      # Subdivide blue triangle
      q = a - b
      q /= PHI
      q += b
      r = c - b
      r /= PHI
      r += b
      result << Tile.new(1, r, c, a)
      result << Tile.new(1, q, r, b)
      result << Tile.new(0, r, q, a)
    end
    return result
  end
end

class ATile < Tile
  def subdivide
    result = []
    if (col == 0)
      # Subdivide red (half kite) triangle
      q = b - a
      q /= PHI
      q += a
      r = c - b
      r /= PHI
      r += b
      result << ATile.new(1, r, q, b)
      result << ATile.new(0, q, a, r)
      result << ATile.new(0, c, a, r)
    else
      # Subdivide blue (half dart) triangle
      p = a - c
      p /= PHI
      p += c
      result << ATile.new(1, b, p, a)
      result << ATile.new(0, p, c, b)
    end
    return result
  end
end

Here is a screenshot, before I added second checkbox

Wednesday 8 January 2014

More keyword argument experiments with JRubyArt

Here we create a factory module, that allows us to create a RubyStruct (entry order important) using a keyword hash (order not important).
#
# Esfera
# by David Pena.  
# Somewhat re-factored for ruby-processing
# by Martin Prout
# Distribucion aleatoria uniforme sobre la superficie de una esfera. 
#

QUANTITY = 16000

attr_reader :orb, :radius, :rx, :ry

# signature-specific aliases for overloaded methods
java_alias :fill_int, :fill, [Java::int]
java_alias :stroke_int, :stroke, [Java::int]
java_alias :stroke_float_float, :stroke, [Java::float, Java::float]

module HairFactory
  Hair = Struct.new(:z, :phi, :len, :theta)
  def self.hair(args)
    Hair.new(
      args[:z],
      args[:phi],
      args[:len],
      args[:theta]
      )
    end
end

def setup
  size(800, 600, P3D)
  @rx = 0
  @ry = 0
  no_smooth
  @radius = height/3.5
  @orb = []
  # lets define some ruby lambdas
  rnd = ->(rad){rand(-rad .. rad)}
  asine = ->(zl, rl){Math.asin(zl / rl)}
  rl = ->(a, b){rand(a .. b)}
  rpi = -> {rand * Math::PI * 2}

  # lets populate the orb with hairs
  QUANTITY.times do
    z = rnd.call(radius) # needed twice so need to define outside constructor
    orb << HairFactory.hair(  # note by using keyword arguments their order is not important
      z: z,
      phi: rpi.call,
      len: rl.call(1.15, 1.2),
      theta: asine.call(z, radius)
      )
  end
  noise_detail(3)
end

def draw
  off = ->(a, b, c){a + ((noise(c * 0.0005, Math.sin(b)) - 0.5) * 0.3)}
  offb = ->(a, b, c){a + ((noise(c * 0.0007, Math.sin(b) * 0.01) - 0.5) * 0.3)}
  background(0)
  translate(width/2,height/2)
  rxp = ((mouse_x - (width/2))*0.005)
  ryp = ((mouse_y - (height/2))*0.005)
  @rx = (rx*0.9)+(rxp*0.1)
  @ry = (ry*0.9)+(ryp*0.1)
  rotate_y(rx)
  rotate_x(ry)
  fill_int 0
  no_stroke
  sphere(radius)
  orb.each do |hair|
    thetaff = off.call(hair.theta, hair.phi, millis())
    costhetaff = Math.cos(thetaff)
    coshairtheta = Math.cos(hair.theta)
    phff = offb.call(hair.phi, hair.z, millis())
    xa = radius * coshairtheta * Math.cos(hair.phi)
    ya = radius * coshairtheta * Math.sin(hair.phi)
    za = radius * Math.sin(hair.theta)
    xo = radius * costhetaff * Math.cos(phff)
    yo = radius * costhetaff * Math.sin(phff)
    zo = radius * sin(thetaff)
    xb, yb, zb = xo * hair.len, yo * hair.len, zo * hair.len
    stroke_weight(1)
    begin_shape(LINES)
    stroke_int(0)
    vertex(xa, ya, za)
    stroke_float_float(200, 150)
    vertex(xb, yb, zb)
    end_shape()
  end
  if (frame_count % 10 == 0)
    puts(frame_rate)
  end
end

Tuesday 7 January 2014

JRubyArt (ruby-processing development) and processing-2.0

It is highly likely that by the time jruby-9000 gets released ruby-2.0 if not ruby-2.1 will be standard. For that reason it is interesting to look at least at ruby-2.0 if not ruby-2.1 to see if the new features would be useful in ruby-processing. The new keyword argument syntax in particular looks quite interesting and I have included it in a revised version of the boids library (rboids in JRubyArt).
#####
# Original version
#####
def update(opts={}) # Just flutter, little boids ... just flutter away.         
  options = {
    shuffled: true, # Shuffling keeps things flowing smooth.
    cohesion: 100.0,
    separation: 10.0,
    alignment: 5.0,
    goal: 20.0,
    limit: 30.0
  }
  options.merge! opts
  # .... use options[var] in method body 
  # ....
end

#####
#  Updated version 
#  Featuring ruby 2.0 syntax
#####

def update(goal: 20.0, limit: 30.0, **opts) # Just flutter, little boids ... just flutter away.         

  shuffled = opts.fetch(:shuffled, true) # Shuffling keeps things flowing smooth.
  cohesion = opts.fetch(:cohesion, 100.0)
  separation = opts.fetch(:separation, 10.0)
  alignment = opts.fetch(:alignment, 5.0)
  # .... use variable directly in method body
  # ....
end

Previously opts = {} was used as the argument, and default goal and limit needed to be defined in a separate options hash, that was merged.
It occurs to me it could possibly be useful more generally where java methods are heavily overloaded eg camera (and entry order is annoyingly important under java). I do not anticipate using it for background, fill etc however. NB: remember to run in ruby-2.0 mode (eg by setting compat.version=2.0 in .jrubyrc).

Sunday 5 January 2014

Revisiting yaml load and save for jruby-2.0

In the previous post I inclube the "bubble" library which I re-use here in my re-factored load_and_save_yaml.rb sketch (JRubyArt the development branch of ruby-processing). Hereis the yaml:-
---
bubbles:
- :x: 160
  :y: 103
  :diameter: 43.19838
  :label: Happy
- :x: 372
  :y: 137
  :diameter: 52.42526
  :label: Sad
- :x: 273
  :y: 235
  :diameter: 61.14072
  :label: Joyous
- :x: 121
  :y: 179
  :diameter: 44.758068
  :label: Melancholy

Here is the JRubyArt sketch:-
######################################
# Yet another examples of reading and
# writing to some form of markup,
# appropriately yaml.
# by Martin Prout after Dan Shiffman
# updated for ruby-2.0 (Struct to Hash)
# ###################################
load_library :bubble

attr_reader :bubble_data

def setup()
  size(640, 360)
  # load data from file
  @bubble_data = BubbleData.new "bubbles"
  bubble_data.load_data "data/data.yml"
end

def draw
  background 255
  bubble_data.display mouse_x, mouse_y
end

def mouse_pressed
  # create a new bubble instance, where mouse was clicked
  @bubble_data.create_new_bubble(mouse_x, mouse_y)
end

class BubbleData
  include Enumerable

  MAX_BUBBLE = 10

  attr_reader :key, :path, :bubbles
  def initialize key
    @key = key
    @bubbles = []
  end

  def each &block
    bubbles.each &block
  end

  def create_new_bubble x, y
    self << Bubble.new(x, y, rand(40 .. 80), "new label")
    save_data
    load_data path
  end

  def display x, y
    self.each do |bubble|
      bubble.display
      bubble.rollover(x, y)
    end
  end

  # @param path to yaml file

  def load_data path
    @path = path
    yaml = Psych.load_file("data/data.yml")
    data = yaml[key]
    bubbles.clear
    # iterate the bubble_data array, and create an array of bubbles
    data.each do |point|
      self << Bubble.new(
        point[:x],
        point[:y],
        point[:diameter],
        point[:label])
    end
  end


  def << bubble
    bubbles << bubble
    bubbles.shift if bubbles.size > MAX_BUBBLE
  end

  private

  def save_data
    hash = { key => self.map{ |point| point.to_h} }
    yaml = hash.to_yaml
    # overwite existing 'data.yaml' 
    open("data/data.yml", 'w:UTF-8') {|f| f.write(yaml) }
  end

end

Revisiting json load and save for ruby-2.0

Since ruby-2.0 Struct to Hash is supported (to_h) so I thought I would give it a go with JRubyArt. The first thing you need to do is to set jruby to run in 2.0 mode, this is easily achieved by setting the compatibility version in your .jrubyrc:-
compat.version=2.0
But there are other ways to do it!
The json:-
{
  "bubbles": [
    {
      "x": 160,
      "y": 103,
      "diameter": 43.19838,
      "label": "Happy"
    },
    {
      "x": 372,
      "y": 137,
      "diameter": 52.42526,
      "label": "Sad"
    },
    {
      "x": 273,
      "y": 235,
      "diameter": 61.14072,
      "label": "Joyous"
    },
    {
      "x": 121,
      "y": 179,
      "diameter": 44.758068,
      "label": "Melancholy"
    }
  ]
}

The sketch:-
# This example demonstrates how easily "sketch data" can be retrieved from a json file
# in ruby-processing. Note this sketch re-uses the Bubble class from the bubble library. 
# The BubbleData class, can load, store and create instances of Bubble (and request them
# to display and/or show their label, when 'mouse over').
# @author Martin Prout, after Daniel Shiffmans version for processing, updated for ruby-2.0
# 
load_library :bubble
require "json"


attr_reader :bubble_data

def setup()
  size(640, 360)
  # initialize bubble_data with 'key' and read data from 'file path'
  @bubble_data = BubbleData.new "bubbles"
  bubble_data.load_data "data/data.json"
end

def draw
  background 255
  # draw the bubbles and display a bubbles label whilst mouse over
  bubble_data.display mouse_x, mouse_y
end

def mouse_pressed
  # create a new instance of bubble, where mouse was clicked
  bubble_data.create_new_bubble(mouse_x, mouse_y)
end




class BubbleData
  include Enumerable

  MAX_BUBBLE = 10

  attr_reader :key, :path, :bubbles

  # @param key String for top level hash

  def initialize key
    @key = key
    @bubbles = []
  end

  def each &block
    bubbles.each &block
  end

  def create_new_bubble x, y
    self << Bubble.new(x, y, rand(40 .. 80), "new label")
    save_data
    load_data path
  end

  def display x, y
    self.each do |bubble|
      bubble.display
      bubble.rollover(x, y)
    end
  end

  # @param path to json file

  def load_data path
    @path = path
    source_string = open(path, "r"){ |file| file.read }
    data = JSON.parse(source_string)[key]
    bubbles.clear
    # iterate the bubble_data array, and create an array of bubbles
    data.each do |point|
      self << Bubble.new(
        point["x"],
        point["y"],
        point["diameter"],
        point["label"])
    end
  end

  def << bubble
    bubbles << bubble
    bubbles.shift if bubbles.size > MAX_BUBBLE
  end

  private

  def save_data
    hash = { key => self.map{ |point| point.to_h } }
    json = JSON.pretty_generate(hash)      # generate pretty output
    open(path, 'w') { |f| f.write(json) }
  end
end

The bubble library:-
# The bubble library, include BubbleStruct

class Bubble
  include Processing::Proxy

  attr_reader :data, :over

  # Create  the Bubble
  def initialize(x, y, diameter, label)
    @data = BubbleStruct.new(x, y, diameter, label)
    @over = false
  end

  # Checking if mouse is over the Bubble
  def rollover(px, py)
    d = dist(px,py,data.x,data.y)
    @over = (d < data.diameter/2)? true : false
  end

  # Display the Bubble
  def display
    stroke(0)
    stroke_weight(2)
    no_fill
    ellipse(data.x, data.y, data.diameter, data.diameter)
    if (over)
      fill(0)
      text_align(CENTER)
      text(data.label, data.x, data.y + data.diameter/2 + 20)
    end
  end

  def to_h
    data.to_h # Struct to Hash since ruby-2.0
  end

end

BubbleStruct = Struct.new(:x, :y, :diameter, :label)

Saturday 4 January 2014

Watch mode working again with P3D sketches in JRubyArt

Since this commit to vanilla processing the watch mode is now working in P3D. This was the result of a pull request from Jonathan Feinberg (processing.py), who realised which resources were not being released from such sketches (it was probably even more important for processing.py). It was Jonathans pull request that alerted me to the fact that it would be worth retesting with the latest development version of processing (which pulls his requested fix). Anyway the upshot is this will be automatically fixed for Linux and Windows users when the next version of processing gets released (Mac users if sticking with version 2.0.3 will not be so lucky). There exists a big opportunity for any Mac user brave enough to test the development versions of both processing and JRubyArt (next version of ruby-processing) to test if it can also work for them.
Anyway following my preliminary testing of "watch mode" with JRubyARt I can make the following suggestions to make your sketches more watch friendly:-
  1. Don't have no_loop in your sketch (it might not get updated) 
  2. If using constants use ||= to define them (that way you avoid complaint of already defined constant)
Anyway if you did not remember to do it before, you can (of course) do it in your first "edit" of the sketch.

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