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

Thursday, 9 September 2010

Translating the default.es to ruby processing DSL

It was very constructive to see if I could translate the EisenScript to my ruby processing DSL, I immediately learnt something about the EisenScript (recursion seems to be implicit, something I had not understood before). What I learnt about my library, was I was losing some context by directly altering the attitude (the image expected from the EisenScript appeared briefly but quickly degraded). I have now re-factored the sketch to only adjust the initial angle, the x rotation seems to affect the amplitude of deformation from the plane of the spirals. The y and z rotation give control of the attitude, and need to be used in combination to fully explore the 3 dimensional shape, which is now somewhat like that produced by StrucureSynth. 

Here is my revised 3d context free DSL (test_free.rb):-

# A Context-Free library for Ruby-Processing, inspired by
# contextfreeart.org and StructureSynth
# Built on Jeremy Ashkenas context free DSL script

module Processing

  class ContextFree


    include Processing::Proxy

    attr_accessor :rules, :app, :xr, :yr, :zr

    AVAILABLE_OPTIONS = [:x, :y, :z, :rx, :ry, :rz, :size, :color, :hue, :saturation, :brightness, :alpha]
    HSB_ORDER         = {:hue => 0, :saturation => 1, :brightness => 2, :alpha => 3}

    # Define a context-free system. Use this method to create a ContextFree
    # object. Call render() on it to make it draw.
    def self.define(&block)
      cf = ContextFree.new
      cf.instance_eval &block
      cf
    end


    # Initialize a bare ContextFree object with empty recursion stacks.
    def initialize
      @app          = $app
      @graphics     = $app.g
      @finished     = false
      @rules        = {}
      @rewind_stack = []
      @matrix_stack = []
      @xr = 0
      @yr = 0
      @zr = 0
    end


    # Create an accessor for the current value of every option. We use a values
    # object so that all the state can be saved and restored as a unit.
    AVAILABLE_OPTIONS.each do |option_name|
      define_method option_name do
        @values[option_name]
      end
    end


    # Here's the first serious method: A Rule has an
    # identifying name, a probability, and is associated with
    # a block of code. These code blocks are saved, and indexed
    # by name in a hash, to be run later, when needed.
    # The method then dynamically defines a method of the same
    # name here, in order to determine which rule to run.
    def rule(rule_name, prob=1, &proc)
      @rules[rule_name] ||= {:procs => [], :total => 0}
      total = @rules[rule_name][:total]
      @rules[rule_name][:procs] << [(total...(prob+total)), proc]
      @rules[rule_name][:total] += prob
      unless ContextFree.method_defined? rule_name
        self.class.class_eval do
          eval <<-METH
            def #{rule_name}(options)
              merge_options(@values, options)
              pick = determine_rule(#{rule_name.inspect})
              @finished = true if @values[:size] < @values[:stop_size]
              unless @finished
                get_ready_to_draw
                pick[1].call(options)
              end
            end
          METH
        end
      end
    end


    # Rule choice is random, based on the assigned probabilities.
    def determine_rule(rule_name)
      rule = @rules[rule_name]
      chance = rand * rule[:total]
      pick = @rules[rule_name][:procs].select {|the_proc| the_proc[0].include?(chance) }
      return pick.flatten
    end


    # At each step of the way, any of the options may change, slightly.
    # Many of them have different strategies for being merged.
    def merge_options(old_ops, new_ops)
      return unless new_ops
      # Do size first
      old_ops[:size] *= new_ops[:size] if new_ops[:size]
      new_ops.each do |key, value|
        case key
        when :size
        when :x, :y, :z
          old_ops[key] = value * old_ops[:size]
        when :rz, :ry, :rx
          old_ops[key] = value * (Math::PI / 180.0)
        when :hue, :saturation, :brightness, :alpha
          adjusted = old_ops[:color].dup
          adjusted[HSB_ORDER[key]] *= value
          old_ops[:color] = adjusted
        when :width, :height
          old_ops[key] *= value
        when :color
          old_ops[key] = value
        else # Used a key that we don't know about or trying to set
          merge_unknown_key(key, value, old_ops)
        end
      end
    end


    # Using an unknown key let's you set arbitrary values,
    # to keep track of for your own ends.
    def merge_unknown_key(key, value, old_ops)
      key_s = key.to_s
      if key_s.match(/^set/)
        key_sym = key_s.sub('set_', '').to_sym
        if key_s.match(/(brightness|hue|saturation|alpha)/)
          adjusted = old_ops[:color].dup
          adjusted[HSB_ORDER[key_sym]] = value
          old_ops[:color] = adjusted
        else
          old_ops[key_sym] = value
        end
      end
    end

    # Doing a 'split' saves the context, and proceeds from there,
    # allowing you to rewind to where you split from at any moment.
    def split(options=nil, &block)
      save_context
      merge_options(@values, options) if options
      yield
      restore_context
    end

    # Saving the context means the values plus the coordinate matrix.
    def save_context
      @rewind_stack.push @values.dup
      @matrix_stack << @graphics.get_matrix
    end

    # Restore the values and the coordinate matrix as the recursion unwinds.
    def restore_context
      @values = @rewind_stack.pop
      @graphics.set_matrix @matrix_stack.pop
    end

    # Rewinding goes back one step.
    def rewind
      @finished = false
      restore_context
      save_context
    end

    # Render the is method that kicks it all off, initializing the options
    # and calling the first rule.
    def render(rule_name, starting_values={})
      @values = {:x => 0, :y => 0, :z => 0,
                 :rz => 0, :ry => 0, :rx => 0,
                 :size => 1, :width => 1, :height => 1,
                 :start_x => width/2, :start_y => height/2, :start_z => 0,
                 :color => [0.5, 0.5, 0.5, 1],
                 :stop_size => 1.5}
      @values.merge!(starting_values)
      @finished = false
      @app.reset_matrix
      @app.no_stroke
      @app.color_mode HSB, 1.0
      @app.translate @values[:start_x], @values[:start_y], @values[:start_z]
      self.send(rule_name, {})
    end

    def rotate_x rt
      @xr = rt
    end
    def rotate_y rt
      @yr = rt
    end
    def rotate_z rt
      @zr = rt
    end

    # Before actually drawing the next step, we need to move to the appropriate
    # location.
    def get_ready_to_draw
      @app.translate(@values[:x], @values[:y], @values[:z])
      @app.rotate_x(@values[:rx] + xr)
      @app.rotate_y(@values[:ry] + yr)
      @app.rotate_z(@values[:rz] + zr)
    end


    # Compute the rendering parameters for drawing a shape.
    def get_shape_values(some_options)
      old_ops = @values.dup
      merge_options(old_ops, some_options) if some_options
      @app.fill *old_ops[:color]
      return old_ops[:size], old_ops
    end


    # Sphere, cube are the primitive drawing
    def cube(some_options=nil)
      size, options = *get_shape_values(some_options)
      rotz = options[:rz]
      roty = options[:ry]
      rotx = options[:rx]
      @app.rotate_x rotx unless rotx.nil?
      @app.rotate_y roty unless roty.nil?
      @app.rotate_z rotz unless rotz.nil?
      @app.translate(options[:x]  * size, options[:y] * size , options[:z]  * size)
      @app.box(size)
      @app.rotate_z(-1 * rotz) unless rotz.nil?  # unwind rotations in an oredered way
      @app.rotate_y(-1 * roty) unless roty.nil?
      @app.rotate_x(-1 * rotx) unless rotx.nil?    
  
    end


    def sphere(some_options=nil)
      size, options = *get_shape_values(some_options)
      rotz = options[:rz]
      roty = options[:ry]
      rotx = options[:rx]
      @app.rotate_x rotx unless rotx.nil?
      @app.rotate_y roty unless roty.nil?
      @app.rotate_z rotz unless rotz.nil?
      @app.sphere_detail 10
      @app.sphere(size)
      @app.rotate_z(-1 * rotz) unless rotz.nil?  # unwind rotations in an oredered way
      @app.rotate_y(-1 * roty) unless roty.nil?
      @app.rotate_x(-1 * rotx) unless rotx.nil?    
    end
  end
end





My Translation of default.es to ruby processing



# default.rb
# virtually a direct translation of default.es
# the StructureSynth default script

load_libraries :test_free, :control_panel

attr_reader :amplitude, :yrot, :zrot

def setup_the_spiral
  @spiral = ContextFree.define do

    rule :default do
      split do
      R1 :brightness => 1
      rewind
      R2 :brightness => 1
    end
    end

    rule :R1 do                      
      sphere :brightness => 1
      R1 :size => 0.99, :x => 0.25, :rz => 6, :ry => 6
    end

    rule :R2 do                      
      sphere :brightness => 1
      R2 :size => 0.99, :x => -0.25, :rz => 6, :ry => 6
    end

  end
end

def configure_control # setup control panel gui
  control_panel do |c|
    c.title = "Amplitude+Attitude"
    c.slider :amplitude, -0.025..0.015, 0.0
    c.slider :yrot, -6.3..6.3, 0.005
    c.slider :zrot, -6.3..6.3, 0.005
  end
end

def setup
  size 800, 800, P3D
  configure_control
  smooth
  setup_the_spiral
end

def draw
  background 0
  lights
  @spiral.render :default, :start_x => 0, :start_y => 0, :start_z => -40, :size => height/400,
  :stop_size => 0.2, :color => [0, 0.8, 0.8], :rx => amplitude, :ry => yrot, :rz => zrot

end



Testing the cube primitive in my context free DSL

Another context free DSL example, better annotated, control panel and library as previous post (not shown):-


# cube.rb

load_libraries :test_free, :control_panel

attr_reader :xrot, :yrot, :zrot

def setup_the_spiral
  @spiral = ContextFree.define do  # define the cf rules
   
    rule :cubeform do
      5.times do |i|
        split do                   # record the context
        cuby :ry => 72 * i
        rewind                     # restore the context
        end
      end
    end

    rule :beam do                      
       6.times do |i|
        split do                    # record the context
          cube :y => 0.2 * i
        rewind                      # restore the context
        end
      end
    end
 
    rule :cuby do
      beam :brightness => 1
      cuby :size => 0.98, :y => -0.24  # recursive call ends when size < 1
    end

  end
end

def setup              # static setup
  size 800, 800, P3D   # I can only use P3D (unless full_screen) on linux opengl might work on mac/windows
  configure_control
  smooth
  setup_the_spiral
end

def configure_control # setup control panel gui
  control_panel do |c|
    c.title = "Attitude Control"
    c.slider :xrot, -3.1..3.1, 0.5
    c.slider :yrot, -3.1..3.1, 0.5
    c.slider :zrot, -3.1..3.1, 1.0
  end
end

def draw             # animation loop    
  background 0.8     
  lights
  @spiral.render :cubeform, :start_x => 0, :start_y => 10, :start_z => -30, :size => height/300, :stop_size => 1, :color => [0, 0.8, 0.8]
  @spiral.rotate_x xrot
  @spiral.rotate_y yrot
  @spiral.rotate_z zrot   
end


 


Within the sketch :hue, :saturation and :brightness can be individually set. If you want to use :alpha, initialize :color with a 4 dimension array. Note, the color mode in the ruleset is hsb with range 0 .. 1.0. Increment and decrement is linear cf size where it is multiplicative.

Wednesday, 8 September 2010

Experimental 3D Context Free DSL in ruby-processing, a bit of a monster?

This is at fairly experimental stage, I need to decide how to order rotations/translations. At the moment order is fixed by the library script, so it doesn't matter what order they are entered you get the default order!!!

This library cannot compete with StructureSynth which is a much more refined and complete program. However if you are used to ruby it may be easier to write your rules in ruby, rather than in the EisenScript syntax. The funky extra thing is, because it is based on processing you have the animation possibilities of the 'draw' loop. My monster in some directions could be seen as a beating heart.

To use the library, create a folder for you work say "work", create a sub-folder "library" and sub-sub-folder "test_free" (or whatever better name you can think of for the library). Put the library "test_free.rb" in the sub-sub-folder. Write you sketches in the "work" directory, and run them with "rp5 run my_sketch.rb". If you want to do it the really neat way use JEdit as your editor and my macro and commando tools, then you can run the sketch from the editor. The link to the tools describes live editing, which I haven't tried with this library so I would recommend just using the run mode for now.

If you are not a ruby meddler just get StructureSynth, and learn the EisenScript language. Otherwise you will need to get ruby-processing see link in my blog header to see how. My blog header also has link to the original Jeremy Ashkenas context-free.rb on github.

The test_free.rb library


# A Context-Free library for Ruby-Processing, inspired by
# contextfreeart.org and StructureSynth
# Built on Jeremy Ashkenas context free DSL script

module Processing

  class ContextFree
 

    include Processing::Proxy

    attr_accessor :rules, :app, :xr, :yr, :zr

    AVAILABLE_OPTIONS = [:x, :y, :z, :rx, :ry, :rz, :size, :color, :hue, :saturation, :brightness, :alpha]
    HSB_ORDER         = {:hue => 0, :saturation => 1, :brightness => 2, :alpha => 3}

    # Define a context-free system. Use this method to create a ContextFree
    # object. Call render() on it to make it draw.
    def self.define(&block)
      cf = ContextFree.new
      cf.instance_eval &block
      cf
    end


    # Initialize a bare ContextFree object with empty recursion stacks.
    def initialize
      @app          = $app
      @graphics     = $app.g
      @finished     = false
      @rules        = {}
      @rewind_stack = []
      @matrix_stack = []
      @xr = 0
      @yr = 0
      @zr = 0
    end


    # Create an accessor for the current value of every option. We use a values
    # object so that all the state can be saved and restored as a unit.
    AVAILABLE_OPTIONS.each do |option_name|
      define_method option_name do
        @values[option_name]
      end
    end


    # Here's the first serious method: A Rule has an
    # identifying name, a probability, and is associated with
    # a block of code. These code blocks are saved, and indexed
    # by name in a hash, to be run later, when needed.
    # The method then dynamically defines a method of the same
    # name here, in order to determine which rule to run.
    def rule(rule_name, prob=1, &proc)
      @rules[rule_name] ||= {:procs => [], :total => 0}
      total = @rules[rule_name][:total]
      @rules[rule_name][:procs] << [(total...(prob+total)), proc]
      @rules[rule_name][:total] += prob
      unless ContextFree.method_defined? rule_name
        self.class.class_eval do
          eval <<-METH
            def #{rule_name}(options)
              merge_options(@values, options)
              pick = determine_rule(#{rule_name.inspect})
              @finished = true if @values[:size] < @values[:stop_size]
              unless @finished
                get_ready_to_draw
                pick[1].call(options)
              end
            end
          METH
        end
      end
    end


    # Rule choice is random, based on the assigned probabilities.
    def determine_rule(rule_name)
      rule = @rules[rule_name]
      chance = rand * rule[:total]
      pick = @rules[rule_name][:procs].select {|the_proc| the_proc[0].include?(chance) }
      return pick.flatten
    end


    # At each step of the way, any of the options may change, slightly.
    # Many of them have different strategies for being merged.
    def merge_options(old_ops, new_ops)
      return unless new_ops
      # Do size first
      old_ops[:size] *= new_ops[:size] if new_ops[:size]
      new_ops.each do |key, value|
        case key
        when :size
        when :x, :y, :z
          old_ops[key] = value * old_ops[:size]
        when :rz, :ry, :rx
          old_ops[key] = value * (Math::PI / 180.0)
        when :hue, :saturation, :brightness, :alpha
          adjusted = old_ops[:color].dup
          adjusted[HSB_ORDER[key]] *= value
          old_ops[:color] = adjusted
        when :width, :height
          old_ops[key] *= value
        when :color
          old_ops[key] = value
        else # Used a key that we don't know about or trying to set
          merge_unknown_key(key, value, old_ops)
        end
      end
    end


    # Using an unknown key let's you set arbitrary values,
    # to keep track of for your own ends.
    def merge_unknown_key(key, value, old_ops)
      key_s = key.to_s
      if key_s.match(/^set/)
        key_sym = key_s.sub('set_', '').to_sym
        if key_s.match(/(brightness|hue|saturation|alpha)/)
          adjusted = old_ops[:color].dup
          adjusted[HSB_ORDER[key_sym]] = value
          old_ops[:color] = adjusted
        else
          old_ops[key_sym] = value
        end
      end
    end

    # Doing a 'split' saves the context, and proceeds from there,
    # allowing you to rewind to where you split from at any moment.
    def split(options=nil, &block)
      save_context
      merge_options(@values, options) if options
      yield
      restore_context
    end

    # Saving the context means the values plus the coordinate matrix.
    def save_context
      @rewind_stack.push @values.dup
      @matrix_stack << @graphics.get_matrix
    end

    # Restore the values and the coordinate matrix as the recursion unwinds.
    def restore_context
      @values = @rewind_stack.pop
      @graphics.set_matrix @matrix_stack.pop
    end

    # Rewinding goes back one step.
    def rewind
      @finished = false
      restore_context
      save_context
    end

    # Render the is method that kicks it all off, initializing the options
    # and calling the first rule.
    def render(rule_name, starting_values={})
      @values = {:x => 0, :y => 0, :z => 0,
                 :rz => 0, :ry => 0, :rx => 0,
                 :size => 1, :width => 1, :height => 1,
                 :start_x => width/2, :start_y => height/2, :start_z => 0,
                 :color => [0.5, 0.5, 0.5, 1],
                 :stop_size => 1.5}
      @values.merge!(starting_values)
      @finished = false
      @app.reset_matrix
      @app.rect_mode CENTER
      @app.ellipse_mode CENTER
      @app.no_stroke
      @app.color_mode HSB, 1.0
      @app.translate @values[:start_x], @values[:start_y], @values[:start_z]
      self.send(rule_name, {})
    end
 
    def rotate_x rt
      @xr = rt
    end
    def rotate_y rt
      @yr = rt
    end
    def rotate_z rt
      @zr = rt
    end

    # Before actually drawing the next step, we need to move to the appropriate
    # location.
    def get_ready_to_draw
      @app.translate(@values[:x], @values[:y], @values[:z])
      @app.rotate_x(@values[:rx] + xr)
      @app.rotate_y(@values[:ry] + yr)
      @app.rotate_z(@values[:rz] + zr)
    end


    # Compute the rendering parameters for drawing a shape.
    def get_shape_values(some_options)
      old_ops = @values.dup
      merge_options(old_ops, some_options) if some_options
      @app.fill *old_ops[:color]
      return old_ops[:size], old_ops
    end


    # The shape primitives are sphere and cube
    def cube(some_options=nil)
      size, options = *get_shape_values(some_options)
      rotz = options[:rz]
      roty = options[:ry]
      rotx = options[:rx]
      @app.rotate_x rotx unless rotx.nil?
      @app.rotate_y roty unless roty.nil?
      @app.rotate_z rotz unless rotz.nil?
      @app.translate(options[:x]  * size, options[:y] * size , options[:z]  * size)
      @app.box(size)
      @app.rotate_z(-1 * rotz) unless rotz.nil?  # unwind rotations in an ordered way
      @app.rotate_y(-1 * roty) unless roty.nil?
      @app.rotate_x(-1 * rotx) unless rotx.nil?    
   
    end


    def sphere(some_options=nil)
      size, options = *get_shape_values(some_options)
      rotz = options[:rz]
      roty = options[:ry]
      rotx = options[:rx]
      @app.rotate_x rotx unless rotx.nil?
      @app.rotate_y roty unless roty.nil?
      @app.rotate_z rotz unless rotz.nil?

       @app.translate(options[:x]  * size, options[:y] * size , options[:z]  * size)

      @app.sphere_detail 10
      @app.sphere(size)
      @app.rotate_z(-1 * rotz) unless rotz.nil?  # unwind rotations in an ordered way
      @app.rotate_y(-1 * roty) unless roty.nil?
      @app.rotate_x(-1 * rotx) unless rotx.nil?    
    end
  end
end




My Test Script monster.rb



load_libraries :test_free, :control_panel

attr_reader :xrot, :yrot, :zrot

def setup_the_spiral
  @spiral = ContextFree.define do
    rule :monster do
      5.times do |i|
        split do
        cone :ry => 72 * i
        rewind
        end
      end
    end

    rule :cone do                       # two equally weighted cone rules creates the beat
      sphere :brightness => 1
      cone :size => 0.96, :y => -0.24
    end
  
    rule :cone do
      sphere :brightness => 1
      cone :size => 0.97, :y => -0.28
    end

  end
end

def setup
  size 800, 800, P3D
  configure_control
  smooth
  setup_the_spiral
end

def configure_control
  control_panel do |c|
    c.title = "Attitude Control"
    c.slider :xrot, -3.1..3.1, 0.5
    c.slider :yrot, -3.1..3.1, 0.5
    c.slider :zrot, -3.1..3.1, 1.0
  end
end
def draw
  background 0.8
  lights
  @spiral.render :monster, :start_x => 0, :start_y => 10, :start_z => -30, :size => height/200, :stop_size => 1, :color => [0, 0.8, 0.8]
  @spiral.rotate_x xrot
  @spiral.rotate_y yrot
  @spiral.rotate_z zrot    
end



 


Saturday, 4 September 2010

Using the control panel and Toxi Processing Libraries in ruby processing

Here is a sketch that shows how easy it is to add controls to your sketch in ruby-processing.


#
# <p>GrayScottControl uses the seedImage() method to use a bitmap as simulation seed.
# In this demo the image is re-applied every frame and the user can adjust the
# F coefficient of the reaction diffusion to produce different patterns emerging
# from the boundary of the bitmapped seed. Unlike some other GS demos provided,
# this one also uses a wrapped simulation space, creating tiled patterns.</p>
#
# <p><strong>usage:</strong></p>
# <ul>
# <li>click + drag mouse to locally disturb the simulation</li>
# <li>Use slider to adjust the F parameter of the simulation</li>
# <li>press reset! to do what?</li>
# <li>press save_graycsott to do save</li>
# </ul>
#

#
# Copyright (c) 2010 Karsten Schmidt (rubified by Martin Prout)
#
# This demo & library is free software you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation either
# version 2.1 of the License, or (at your option) any later version.
#
# http:#creativecommons.org/licenses/LGPL/2.1/
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#

class GrayScottControl < Processing::App
  load_libraries 'simutils','toxiclibscore','colorutils','control_panel'
  include_package "toxi.sim.grayscott"
  include_package "toxi.math"
  include_package "toxi.color"

  attr_reader :gs, :tone_map, :img, :set_f

  def setup
    size 256, 256
    setup_control
    @gs = GrayScott.new width, height, true
    @img = load_image "two_prong.png"
    # create a duo-tone gradient map with 256 steps
    # NB: use '::' in place of '.' here
    @tone_map = ToneMap.new(0, 0.33, NamedColor::CRIMSON, NamedColor::WHITE, 256)
  end

  def draw
    @gs.seed_image(img.pixels, img.width, img.height)
    @gs.set_rect(mouse_x, mouse_y, 20, 20) if mouse_pressed?
    load_pixels
    10.times { @gs.update(1) }
    # read out the V result array
    # and use tone map to render colours
    gs.v.length.times do |i|
      pixels[i]=tone_map.getARGBToneFor(gs.v[i])  # NB: don't camel case convert here
    end
    @gs.setF(0.02 + set_f * 0.001)
    update_pixels
  end

  def setup_control
    control_panel do |c|
      c.title = "Control Panel"
      c.slider :set_f, 0..9, 8
      c.button :save_graycsott
      c.button :reset!
    end
  end

  def save_graycsott
    no_loop
    save_frame "toxi.png"
    loop
  end

  def reset!
    @gs.reset
  end

end




Thursday, 2 September 2010

Tone map example of Gray-Scott diffusion using toxiclibs in ruby processing

Another rubified example, showing how to use some of the toxiclibs in ruby-processing. Any observations/questions could be made on the processing alternative implementations forum, which I often watch.


#
# <p>GrayScottToneMap shows how to use the ColorGradient & ToneMap classes of the
# colorutils package to create a tone map for rendering the results of
# the Gray-Scott reaction-diffusion.</p>
#
# <p><strong>Usage:</strong><ul>
# <li>click + drag mouse to draw dots used as simulation seed</li>
# <li>press any key to reset</li>
# </ul></p>
#

#
# Copyright (c) 2010 Karsten Schmidt
#
# This demo & library is free software you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation either
# version 2.1 of the License, or (at your option) any later version.
#
# http://creativecommons.org/licenses/LGPL/2.1/
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#

class GrayScottToneMap < Processing::App
  load_libraries 'simutils','toxiclibscore','colorutils'
  include_package "toxi.sim.grayscott"
  include_package "toxi.math"
  include_package "toxi.color"
 
  NUM_ITERATIONS = 10

  attr_reader :gs, :tone_map

  def setup()
    size(256,256)
    @gs= GrayScott.new width,height, false
    @gs.set_coefficients 0.021, 0.076, 0.12, 0.06
    # create a color gradient for 256 values
    grad = ColorGradient.new
    # NamedColors are preset colors, but any TColor can be added
    # see javadocs for list of names:
    # http://toxiclibs.org/docs/colorutils/toxi/color/NamedColor::html
    # NB: use '::' in place of '.' here for these java constants
    grad.add_color_at(0, NamedColor::BLACK)
    grad.add_color_at(128, NamedColor::RED)
    grad.add_color_at(192, NamedColor::YELLOW)
    grad.add_color_at(255, NamedColor::WHITE)
    # this gradient is used to map simulation values to colors
    # the first 2 parameters define the min/max values of the
    # input range (Gray-Scott produces values in the interval of 0.0 - 0.5)
    # setting the max = 0.33 increases the contrast
    @tone_map = ToneMap.new 0, 0.33, grad
  end
 
  def draw()    
    @gs.set_rect(mouse_x, mouse_y, 20, 20) if mouse_pressed?
    load_pixels
    # update the simulation a few time steps
    NUM_ITERATIONS.times { @gs.update(1) }
    # read out the V result array
    # and use tone map to render colours
    gs.v.length.times do |i|
      pixels[i]=tone_map.getARGBToneFor(gs.v[i])  # NB: don't camel case convert here
    end
    update_pixels
  end
   
  def key_pressed
    @gs.reset()
  end

 
end



 

Wednesday, 1 September 2010

Another Example of Using Toxi Processing Libraries

Here is another rubified example, a sophisticated variant of Gray-Scott diffusion, using toxis processing libraries, this time using a bit mapped seed:-


#
# <p>GrayScottImage uses the seedImage() method to use a bitmap as simulation seed.
# In this demo the image is re-applied every frame and the user can adjust the
# F coefficient of the reaction diffusion to produce different patterns emerging
# from the boundary of the bitmapped seed. Unlike some other GS demos provided,
# this one also uses a wrapped simulation space, creating tiled patterns.</p>
#
# <p><strong>usage:</strong></p>
# <ul>
# <li>click + drag mouse to locally disturb the simulation</li>
# <li>press 1-9 to adjust the F parameter of the simulation</li>
# <li>press any other key to reset</li>
# </ul>
#

#
# Copyright (c) 2010 Karsten Schmidt
#
# This demo & library is free software you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation either
# version 2.1 of the License, or (at your option) any later version.
#
# http:#creativecommons.org/licenses/LGPL/2.1/
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#

class GrayScottImage < Processing::App
  load_libraries 'simutils','toxiclibscore','colorutils'
  include_package "toxi.sim.grayscott"
  include_package "toxi.math"
  include_package "toxi.color"

  attr_reader :gs, :tone_map, :img

  def setup
    size 256, 256
    @gs = GrayScott.new width, height, true
    @img = load_image "ti_yong.png"
    # create a duo-tone gradient map with 256 steps
    # NB: use '::' in place of '.' here for these java constants
    @tone_map = ToneMap.new(0,  0.33, NamedColor::CRIMSON, NamedColor::WHITE, 256)
  end

  def draw
    @gs.seed_image(img.pixels, img.width, img.height)
    @gs.set_rect(mouse_x, mouse_y, 20, 20) if mouse_pressed?
    load_pixels
    10.times { @gs.update(1) }
    # read out the V result array
    # and use tone map to render colours
    gs.v.length.times do |i|
      pixels[i]=tone_map.getARGBToneFor(gs.v[i])  # NB: don't camel case convert here
    end
    update_pixels
  end

  def key_pressed
    case key
    when '1', '9', '3', '2', '4', '6', '5', '7', '8'
      @gs.setF(0.02 + (key.to_i - 1) * 0.001)
    when 's'
      save_frame "toxi.png"
    else
      @gs.reset()
    end
  end

end


 


I found that the image 'needed' to be 'disturbed' by a mouse-click/drag, before the numeric controls kicked in (it made me doubt my coding originally).

Using the Toxi Processing Libraries in ruby processing

Toxi aka Karsten Schmidt has created some fantastic libraries for processing that make some otherwise difficult things very easy. Here is just one example that I've tried that could be very useful in generative art.  To use processing libraries in your ruby processing sketch place each jar in folder of the same name as the jar. Then put that folder in library folder, where you've saved the sketch.


#
# <p>Hello Gray-Scott is a very basic demonstration of the underlying
# reaction-diffusion simulation. This model can be used to produce
# a wide variety of patterns, both static and animated and is therefore
# well suited for being combined with other generative techniques.</p>
#
# <p><strong>usage:</strong></p>
# <ul>
# <li>click + drag mouse to draw dots used as simulation seed</li>
# <li>press any key to reset</li>
# </ul>
#

#
# Copyright (c) 2010 Karsten Schmidt
#
# This demo & library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# http://creativecommons.org/licenses/LGPL/2.1/
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#

class HelloGrayScott < Processing::App
  load_libraries 'simutils','toxiclibscore'
  include_package "toxi.sim.grayscott"
  include_package "toxi.math"

  attr_reader :gs

  def setup()
    size(256,256)
    @gs=GrayScott.new width, height, false
    @gs.set_coefficients 0.023, 0.074, 0.095, 0.03
  end

  def draw()
    @gs.set_rect mouse_x, mouse_y, 20, 20 if mouse_pressed?
    load_pixels
    10.times { @gs.update(1) }

    gs.v.length.times do |i|
        val = gs.v[i]*768
        min = (255 <= val)? 255 : val       # processing convenience min(x, y) doesn't work for me
        col= 255 - min.to_i
        pixels[i]=col<<16|col<<8|col|0xff000000
      end
    update_pixels
  end

  def key_pressed
    @gs.reset
  end

end






Reaction Diffusion at Wikipedia

Other interesting links:-

A 3D example in java

A very nice 2D example

http://toxiclibs.org/2010/02/simutils-grayscott/

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