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

Thursday 28 May 2009

Interactive editing using watch

A couple of really neat features you should experiment with when using ruby-processing. One is the interactive editing mode 'rp5 watch my_program.rb' where you can update the code whilst the program is running. Another is the feature $app.find_method("ellipse"), well any other method besides "ellipse" if you will. Heres the code of a little app I was running with the above query, and all the ellipse methods were listed (including the camel-case variants such as ellipseMode ...).


class AppTest < Processing::App
        def setup
                size 100, 100
                puts $app.find_method("vertex")
        end
end


I edited my code to the above substituting "vertex" for ellipse, and on saving the vertex methods (and their camel-case variants) were listed. Pretty cool eh!!

Note that you should avoid camel-case as I get the feeling it is there for backward compatibility, and ruby methods should be preferred over processing where both APIs could be used...

PS: find_method search must be of regex variety, if you try "tan" for example you get 'curveTangent' as well as 'instance_of?' methods listed.

Tuesday 12 May 2009

Nautilus Shell Using Ruby Processing (no magic numbers)

Here we create a nautilus like shape using vertex and line functions, without any magic numbers, well apart from the A and B constants (which are empirically chosen so no magic really). This now old-style, as we can write applets without explicitly including the Processing::App wrapper. The code is implicitly wrapped in an applet which is more like the way things work in the java processing ide see later post (Interactive Ruby and Ruby Processing), I kind of like this style though.

# nautilus.rb
require 'ruby-processing'
class Nautilus < Processing::App
  A = 0.8  # pitch constant
  B = 1.4  # radius constant
  def setup
    translate 230, 120 # offset x and y from origin
    rotate QUARTER_PI + HALF_PI # initial rotation
    smooth
    background 0
    stroke_weight 1
    stroke 255
    for z in 8 .. 40 do # draw the radial lines first (it looks nicer)
      line(get_x(z*A), get_y(z*A), get_x((z-8)*A), get_y((z-8)*A))
    end
    no_fill
    begin_shape # begin spiral 'shell' shape
    stroke_weight 4
    stroke 255, 0, 0
    for i in 0 .. 40 do
      vertex(get_x(i*A), get_y(i*A))
    end
    end_shape
    save_frame "nautilus.png"
  end
  def get_x theta
    A*Math.cos(theta)*Math.exp(theta/Math.tan(B))
  end
   def get_y theta
    A*Math.sin(theta)*Math.exp(theta/Math.tan(B))
  end    
end

Nautilus.new :width => 360, :height => 300, :title => "Approximate Nautilus"

Here is the result:-

Nautilus shell using vertex function

Here is sketch produced using 'magic numbers' calculated using a spreadsheet, iffy method but I like the result.

# vert.rb
require 'ruby-processing'
class Vert < Processing::App
        def setup  
                no_fill  
                smooth  
                stroke_weight 4 
                stroke 255, 0, 0 
                background 0 
                begin_shape # begin spiral 'shell' shape
                vertex 202.5, 200 
                vertex 202.07, 202.07 
                vertex 200, 203.42 
                vertex 197.17, 202.83 
                vertex 195.31, 200 
                vertex 196.12, 196.12 
                vertex 200, 193.58 
                vertex 205.31, 194.69 
                vertex 208.78, 200 
                vertex 207.27, 207.27 
                vertex 200, 212.03 
                vertex 190.05, 209.95 
                vertex 183.53, 200 
                vertex 186.38, 186.38 
                vertex 200, 177.46 
                vertex 218.65, 181.35 
                vertex 230.86, 200 
                vertex 225.54, 225.54 
                vertex 200, 242.26 
                vertex 165.04, 234.96 
                vertex 142.15, 200 
                vertex 152.13, 152.13 
                vertex 200, 120.79 
                vertex 265.53, 134.47 
                vertex 308.44, 200 
                vertex 289.72, 289.72 
                vertex 200, 348.47 
                vertex 77.16, 322.84 
                end_shape  # end spiral 'shell' shape
                stroke_weight 1 # draw the segment lines
                stroke 255         # in lighter color and lighter weight
                line 77.16, 322.84, 165.04, 234.96 
                line 200, 242.26, 200, 348.47 
                line 289.72, 289.72, 225.54, 225.54 
                line 308.44, 200, 230.86, 200 
                line 265.53, 134.47, 218.65, 181.35 
                line 200, 177.46, 200, 120.79 
                line 186.38, 186.38, 152.13, 152.13 
                line 183.53, 200, 142.15, 200 
                line 190.05, 209.95, 165.04, 234.96 
                line 200, 212.03, 200, 242.26 
                line 207.27, 207.27, 225.54, 225.54 
                line 230.86, 200, 208.78, 200 
                line 205.31, 194.69, 218.65, 181.35 
                line 200, 193.58, 200, 177.46 
                line 196.12, 196.12, 186.38, 186.38 
                line 195.31, 200, 183.53, 200 
                line 197.17, 202.83, 190.05, 209.95 
                save_frame "spiral.png" 
        end
end

Vert.new :width => 350, :height => 350, :title => "Nautilus?"


Here is the image, which I think manages that nautilus shell look:-

Thursday 7 May 2009

Animation of a ruby Saucer

Here is my revised ruby saucer animation, very similar to what I produced before using java processing, there are 3 classes, two using the Processing::Proxy mixin that provides the access to Processing api that use of an inner class does for the java implementation:-
The Saucer class ('saucer.rb')


# saucer.rb
class Saucer
  # makes use of mixin to replace java inner class
  include Processing::Proxy
  R1 = 4.0  # class constants that together
  R2 = 3.0  # with PI constants are used
  R3 = 6.0  # to define the Saucer shape
  A1 = 0.25
  A2 = 0.60
  A3 = 0.0
  MAX_THETA = QUARTER_PI/2
  # read accessors
  attr_reader :acc, :loc, :vel, :my_scale, :tilt, :wdth, :hght
  
  def initialize xpos, ypos, acc_y, acc_x, width, height
    @my_scale = 0.0
    @tilt = 0
    @loc = PVector.new xpos, ypos
    @vel = PVector.new 0, 0
    @acc = PVector.new acc_x, acc_y
    @focus = PVector.new xpos, ypos
    @wdth = width # frame width
    @hght = height # frame height
  end
  def height # copes with tilting saucer
    # avoided using ternary statement here
    wd = R3 * 2 * Math.sin(tilt) * my_scale
    ht = R2 * 2 * Math.sin(tilt) * my_scale
    if (ht > wd)
      return ht
    else
      return wd
    end
  end
  def width # copes with tilting saucer
    return R3 * 2 * Math.cos(tilt) * my_scale
  end
  def distance
    @focus.dist loc
  end
  
  def get_point radius, theta
    x = loc.x + (radius * Math.cos(theta + tilt)) *my_scale
    y = loc.y + (radius * Math.sin(theta + tilt)) *my_scale
    return x, y
  end
  def set_tilt(theta)
    theta = Saucer::MAX_THETA if theta > Saucer::MAX_THETA
    theta = -Saucer::MAX_THETA if theta < -Saucer::MAX_THETA
    @tilt = theta
  end
  def set_position x, y
    @loc.x = x
    @loc.y = y
  end
  def set_scale(scale)
    @my_scale = scale unless scale < 0
  end
  def render
    smooth
    curve_tightness(-0.05)
    begin_shape
      x, y = get_point(Saucer::R1, Saucer::A1)
      curve_vertex(x, y)
      x, y = get_point(Saucer::R2, TWO_PI - Saucer::A2)
      curve_vertex(x, y)
      x, y = get_point(Saucer::R2, PI + Saucer::A2)
      curve_vertex(x, y)
      x, y = get_point(Saucer::R1, PI - Saucer::A1)
      curve_vertex(x, y)
      x, y = get_point(Saucer::R2, PI + Saucer::A2)
      vertex(x, y)
      x, y = get_point(Saucer::R3, PI)
      vertex(x, y)
      vertex(x, y)
      x, y = get_point(Saucer::R1, PI - Saucer::A1)
      vertex(x, y)
      vertex(x, y)
      x, y = get_point(Saucer::R1, Saucer::A1)
      vertex(x, y)
      vertex(x, y)
      x, y = get_point(Saucer::R3, Saucer::A3)
      vertex(x, y)
      vertex(x, y)
      end_shape CLOSE
    end
    def update
      @vel.x += acc.x
      @vel.y += acc.y
      @loc.x += vel.x
      @loc.y += vel.y
      set_scale distance/30
      set_tilt vel.x * 0.04
    end
    def reset_x  
      @vel.x = 0
      # ternary statement gets the job done, best used for assignment?
      acc.x < 0 ? @loc.x = self.width/2 : @loc.x = (self.wdth - self.width/2)
      @acc.x *= -1
    end
    def reset_y  
      @vel.y = 0
      acc.y < 0 ? @loc.y = self.height/2 : @loc.y =(self.hght - self.height/2)
      @acc.y *= -1
    end
  end



The starfield animation Star class star.rb



# star.rb author Martin Prout
class Star
        # makes use of mixin to replace java inner class
        include Processing::Proxy
        attr_reader :acc, :loc, :star_size, :vel, :focus # NB accessors used by animation class
        def initialize(sz = 0.0, acc = nil, x = 0.0, y = 0.0)
                @star_size = sz
                @vel = PVector.new
                @acc = acc
                @focus = PVector.new X_FOCUS, Y_FOCUS
                # ensure stars are spread  out from focus initially in correct direction
                @loc = PVector.new(X_FOCUS + acc.x * 8000, Y_FOCUS + acc.y * 5000)
        end
        
        def distance # from focus
                @focus.dist loc
        end
        
        def brightness # star brightness
                distance * 1.3
        end
        
        def update
                @vel.x += acc.x
                @vel.y += acc.y
                @loc.x += vel.x
                @loc.y += vel.y
        end
        # reset_x == reset_y included to have common
        # interface with Saucer class
        def reset_x
                @loc.x = focus.x
                @loc.y = focus.y
                @vel.x = acc.x
                @vel.y = acc.y
        end
        def reset_y
                reset_x
        end
        
        def width
                star_size * 2
        end
        
        def height
                star_size * 2
        end    
        
        def render
                fill 255, 255, 255, brightness
                rect loc.x, loc.y, star_size, star_size
        end
end


Heres the code that drives the saucer animation


=begin
starfield animation, makes use of Processing::Proxy mixin
to have separate Star and Saucer classes with access to Processing
ide revised version as of 3rd July 2009
author Martin Prout
=end
require 'star'
require 'saucer'
# global constants
PHI = (1 + Math.sqrt(5))/2
STARS = 150
X_FOCUS = 800/PHI
Y_FOCUS = 150

class StarfieldSketch < Processing::App
  
  attr_reader :loc, :stars, :saucer

  def setup
    size 800, 500        
    smooth
    no_stroke
    frame_rate 60
    start_x, start_y = get_acceleration 0.005, 0.007
    if start_x.abs < 0.001 || start_y.abs < 0.0005 # guard against v. boring saucer
      start_x = 0.007 # only slightly boring saucer moves right and down initially
      start_y = 0.005
    end
    @saucer = Saucer.new X_FOCUS, Y_FOCUS, start_x, start_y, width, height
    @stars = []
    STARS.times {     # populate with stars, some bigger
      start_x, start_y = get_acceleration 0.002, 0.03
      @stars << Star.new(1.5, PVector.new(start_x, start_y)) unless stars.size % 3 == 0
      @stars << Star.new(2.2, PVector.new(start_x, start_y))  if stars.size % 3 == 0
    }  
  end

  def draw
    background 22, 22, 80
    stars.each { |star|      
      star.render
      star.update
      boundary star
    }
    fill 255, 0, 0  
    @saucer.render
    @saucer.update
    boundary @saucer
  end
  # returns a pair of random accelerations for use in PVector
  def get_acceleration min, max
          magnitude = rand * (max - min) + min
    theta = rand * TWO_PI # randomise direction using ruby rand function
    return magnitude*Math.cos(theta), magnitude*Math.sin(theta)
  end

  def boundary(star)
    star.reset_x unless star.loc.x < width - star.width/2
    star.reset_y unless star.loc.y < height - star.height/2
    star.reset_x unless star.loc.x > star.width/2
    star.reset_y unless star.loc.y > star.height/2
  end
end


To see the animation in action visit http://myweb.tiscali.co.uk/monkstone/animated_saucer/animated_ruby_saucer.html patience is required as whole jruby library is loaded ~9 Mb

Tuesday 5 May 2009

Inner classes, workaround for ruby processing

Developments are moving a pace over on the processing implementation discourse http://processing.org/discourse/yabb2/YaBB.pl?num=1238452139. As you may have realized that classes in processing are generally inner classes, and so have access to methods of the enclosing PApplet class. This is not the same in ruby however Jeremy Ashkenas aka jashkenas has been busy developing a workaround for ruby processing (including processing as a mixin) which seems quite interesting. Here is an example using my Saucer class, I'm not super keen about my render method it doesn't look too ruby like (inherited from my java processing code):-

# saucer.rb

include Math

class Saucer

        include Processing::Proxy

        # include processing as Mixin

        R1 = 4.0  # class constants that together

        R2 = 3.0  # with PI constants are used

        R3 = 6.0  # to define the Saucer shape

        A1 = 0.25

        A2 = 0.60

        A3 = 0.0

        MAX_THETA = QUARTER_PI/2

        attr_reader :xpos, :ypos, :my_scale, :tilt

        def initialize xpos, ypos

                @xpos = xpos

                @ypos = ypos

                @my_scale = 10.0

                @tilt = 0

        end

        def get_point radius, theta

                x = xpos + (radius * Math.cos(theta + tilt)) *my_scale

                y = ypos + (radius * Math.sin(theta + tilt)) *my_scale

                return x, y

        end

        def set_tilt(theta)

                theta = Saucer::MAX_THETA if theta > Saucer::MAX_THETA

                theta = -Saucer::MAX_THETA if theta < -Saucer::MAX_THETA

                @tilt = theta

        end

        def set_scale(scale)

                @my_scale = scale unless scale < 0

        end

        def render

                smooth

                curve_tightness -0.05

                begin_shape

                x, y = get_point(Saucer::R1, Saucer::A1)

                curve_vertex(x, y)

                x, y = get_point(Saucer::R2, TWO_PI - Saucer::A2)

                curve_vertex(x, y)

                x, y = get_point(Saucer::R2, PI + Saucer::A2)

                curve_vertex(x, y)

                x, y = get_point(Saucer::R1, PI - Saucer::A1)

                curve_vertex(x, y)

                x, y = get_point(Saucer::R2, PI + Saucer::A2)

                vertex(x, y)

                x, y = get_point(Saucer::R3, PI)

                vertex(x, y)

                vertex(x, y)

                x, y = get_point(Saucer::R1, PI - Saucer::A1)

                vertex(x, y)

                vertex(x, y)

                x, y = get_point(Saucer::R1, Saucer::A1)

                vertex(x, y)

                vertex(x, y)

                x, y = get_point(Saucer::R3, Saucer::A3)

                vertex(x, y)

                vertex(x, y)

                end_shape CLOSE

        end

end



Here is the main code:-


require 'saucer'

class MixinsTest < Processing::App

        def setup

                @saucer = Saucer.new 70, 50

                @saucer.set_tilt QUARTER_PI/2

                @saucer1 = Saucer.new 130, 170

                @saucer1.set_scale 15

                @saucer1.set_tilt QUARTER_PI/3

                @saucer2 = Saucer.new 210, 340

                @saucer2.set_scale 25

        end

        def draw

                background 11, 17, 67

                fill 116, 39, 39

                @saucer.render

                fill 255, 0, 0

                @saucer1.render

                @saucer2.render

                save_frame("saucer.png")

        end

end



MixinsTest.new :width => 400, :height => 400, :title => "Inner Test"



Heres the result of running the code 'rp5 run main.rb'

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