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

Saturday 30 March 2013

Conways Game Of Life (ruby-processing)

Somewhat more concise code in ruby-processing cf the vanilla-processing original (replaced 0, 1 DEAD-ALIVE with a boolean for which they were only place-holder and hence much scope to dry code).
#
# A Processing implementation of Game of Life
# By Joan Soler-Adillon
#
# Press SPACE BAR to pause and change the cell's values with the mouse
# On pause, click to activate/deactivate cells
# Press R to randomly reset the cells' grid
# Press C to clear the cells' grid
#
# The original Game of Life was created by John Conway in 1970.
#

CELL_SIZE = 5
ALIVE = true
DEAD = false
ALIVE_START = 150
INTERVAL = 100

attr_reader :pause, :cells, :row, :column, :last_time, :alive, :cells_buffer

def setup
  size(960, 640)
  @pause = false
  # Instantiate arrays 
  @row = width / CELL_SIZE
  @column = height / CELL_SIZE
  @cells = Array.new(row) {Array.new(column) {(rand(1000) > ALIVE_START)? DEAD : ALIVE}}
  @last_time = 0
  @alive = color(100, 255, 100)
  # This stroke will draw the background grid (live cells)
  stroke(48, 100)
  noSmooth
end

def draw
  background(0)
  #Draw live cells
  row.times do |x|
    column.times do |y|
      if (cells[x][y])
        fill(alive)
        rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
      end
    end
  end
  # Iterate if timer ticks
  if (millis - last_time > INTERVAL)
    if (!pause)
      tick!
      @last_time = millis
    end
  end

  # Create  new cells manually on pause
  if (pause && mouse_pressed?)
    # # Map and avoid out of bound errors
    xCellOver = (map(mouseX, 0, width, 0, row)).to_i
    xCellOver = constrain(xCellOver, 0, row - 1)
    yCellOver = (map(mouseY, 0, height, 0, column)).to_i
    yCellOver = constrain(yCellOver, 0, column - 1)

    # Check against cells in buffer
    if (cells_buffer[xCellOver][yCellOver])  # Cell is alive
      cells[xCellOver][yCellOver] = DEAD # Kill
      fill(0) #reflect changed status
    else  # Cell is dead
      cells[xCellOver][yCellOver] = ALIVE # Make alive
      fill(alive) # Fill alive color
    end

  elsif (pause && !mouse_pressed?)  # And then save to buffer once mouse goes up
    # Save cells to buffer (so we operate with one array keeping the other intact)
    @cells_buffer = clone2d(cells)
  end
end

def tick!  # When the clock ticks
  # Save cells to buffer (so we operate with one array keeping the other intact)
  @cells_buffer = clone2d(cells)
  # Visit each cell:
  row.times do |x|
    column.times do |y|
      # And visit all the neighbours of each cell
      neighbours = 0 # We'll count the neighbours
      (x - 1 .. x + 1).each do |xx|
        (y - 1 .. y + 1).each do |yy|
          # Make sure you are not out of bounds
          if [(xx>=0), (xx<row), (yy>=0), (yy<column)].all? {|in_bounds| in_bounds == true}
            # Make sure to check against self
            if ![(xx == x), (yy == y)].all? {|is_self| is_self == true}
              if (cells_buffer[xx][yy]) # true == ALIVE
                neighbours += 1 # Check alive neighbours and count them
              end # alive
            end # End of if self
          end # End of if grid bounds
        end # End of yy loop
      end #End of xx loop
      # We've checked the neighbours: apply rules in one line (only in ruby)!
      cells[x][y] = (cells_buffer[x][y])?  ((2 .. 3) === neighbours) : (neighbours == 3)
    end # End of y loop
  end # End of x loop
end # End of function

def key_pressed
  case key
  when 'r', 'R'
    # Restart: reinitialization of cells    
    @cells = Array.new(row) {Array.new(column) {(rand(1000) > ALIVE_START)? DEAD : ALIVE}}
  when ' ' # On/off of pause
    @pause = !pause
  when 'c', 'C' # Clear all
    @cells = Array.new(row) {Array.new(column) {DEAD}}
  end
end

def clone2d array
  result = []
  array.each do |val|
    result << val.clone
  end
  return result
end




Wednesday 20 March 2013

XML example Yahoo Weather

This example is more or less a direct translation of the vanilla processing example to ruby-processing (the main difference is how easy it to create a formatted string in ruby cf java)...
NB: requires current version of ruby-processing-2+
#
# Loading XML Data
# by Daniel Shiffman.  
#
# This example demonstrates how to use loadXML()
# to retrieve data from an XML document via a URI
#

attr_reader :zip, :weather, :temperature

def setup
  size(600, 360)
  @zip = 10003
  font = create_font("Merriweather-Light.ttf", 28)
  text_font(font)

  # The URL for the XML document
  url = "http://xml.weather.yahoo.com/forecastrss?p=#{zip}"

  # Load the XML document
  xml = loadXML(url)

  # Grab the element we want
  forecast = xml.get_child("channel").get_child("item").get_child("yweather:forecast")

  # Get the attributes we want
  @temperature = forecast.get_int("high")
  @weather = forecast.get_string("text")
end

def draw
  background(255)
  fill(0)
  # Display all the stuff we want to display
  text("Zip code: #{zip}", width*0.15, height*0.33)
  text("Today's high: #{temperature}", width*0.15, height*0.5)
  text("Forecast: #{weather}", width*0.15, height*0.66)
end

Demonstrating regex in ruby-processing

There is a example in the vanilla processing distribution, that demonstrates the use of regex (based on java) to extract links from the processing web-site. This more rubified variant extracts only the web addresses using split with a regex. Note this just to demonstrate/explore regex there is the 'uri' module to do this sort of thing if you are serious.
NB: tested with development version of ruby-processing see here.
#
# Regular Expression example
# by Martin Prout.  
# 
# This uses ruby scan
#
# Here we'll load the raw HTML from a URI and search for web-links
#

attr_reader :links, :url

def setup
  size(360, 480)
  @url = "http://processing.org"
  # Load the links
  @links = load_links(url)
  links.uniq! # get rid of the duplicates
  text_font(create_font("Georgia", 16))
end

def draw
  background(0)
  # Display the bare links
  fill(0, 255, 255)
  links.each_with_index do |link, i|
    text(link, 10, 20 + i * 20)
  end
end

def load_links(s)
  result = []
  # Load the raw HTML
  lines = load_strings(s)
  # Put it in one big string
  all_txt = lines.join('\n')
  all_txt.scan(/
        https?:\/\/
        \w+
        (?: [.-]\w+ )*
        (?:
            \/
            [0-9]{1,5}
            \?
            [\w=]*
        )?
    /ix)
end


Saturday 16 March 2013

Another word counting sketch translated to ruby processing

Here is another sketch that was based on a examples by Daniel Schiffman to illustrate the use of a HashMap in vanilla processing.
NB: tested with development version of ruby-processing see here.
Here is the required word.rb:-
#######
# word.rb
# the word class stores data about words (text, source, frequency)
# thanks to processing mixin words are rendered according to
# source and frequency
########
class Word
  include Processing::Proxy
  # Store a count for occurences in two different books
  attr_reader :count_dracula, :count_franken, :total_count, :word, :position, :width, :height, :speed

  def initialize(s)
    @width, @height = $app.width, $app.height
    @count_dracula, @count_franken, @total_count = 0, 0, 0
    @position = [rand(width), rand(-height .. height*2)]
    @word = s
  end

  # We will display a word if it appears at least 5 times
  # and only in one of the books
  def qualify?
    return ((count_dracula == total_count || count_franken == total_count) && total_count > 5)
  end

  # Increment the count for Dracula
  def increment_dracula
    @count_dracula += 1
    @total_count += 1
  end


  # Increment the count for Frankenstein
  def increment_franken
    @count_franken += 1
    @total_count += 1
  end

  # The more often it appears, the faster it falls
  def move
    @speed = map(total_count, 5, 25, 0.1, 0.4).to_f
    @speed = constrain(speed, 0, 10.0)
    @position[Y] += speed

    if (position[Y] > height*2)
      @position[Y] = -height
    end
  end


  # Depending on which book it gets a color
  def display
    if (count_dracula > 0)
      fill(255)
    elsif (count_franken > 0)
      fill(0)
    end
    # Its size is also tied to number of occurences
    fs = map(total_count,5,25,2,24.0).to_f
    fs = constrain(fs, 2, 48)
    text_size(fs)
    text_align(CENTER)
    text(word, position[X], position[Y])
  end
end

Here is the sketch:-
#
# After a HashMap example
# by Daniel Shiffman.  
# 
# This example demonstrates how to use a Hash to store 
# a collection of objects referenced by a key. This is much like an array, 
# only instead of accessing elements with a numeric index, we use a String.
# If you are familiar with associative arrays from other languages,
# this is the same idea.
#
# The Processing classes IntHash, FloatHash, and StringHash offer a simple
# way of pairing Strings with numbers or other Strings. But are probably of 
# less interest to rubyists.
#
# In this example, words that appear in one book (Dracula) are colored white 
# whilst words in the other book (Frankenstein) are colored black.
#
load './word.rb'

DRACULA = "dracula.txt"
FRANKENSTEIN = "frankenstein.txt"

attr_accessor :words

def setup
  size(640, 360)

  # Create the HashMap
  @words = {}
  # Load two files
  load_file(DRACULA)
  load_file(FRANKENSTEIN)
  # Create the font
  text_font(create_font("Georgia", 24))
end

def draw
  background(126)
  # Show words
  words.values.each do |w|
    if w.qualify?
      w.display
      w.move
    end
  end
end

# Load a file
def load_file(filename)
  tokens = File.open(data_path(filename), "r"){|file| file.read.scan(/[\w'-]+/)}
  tokens.each do |s|
    s = s.downcase
    # Is the word in the HashMap
    if (words.has_key?(s))
      # Get the word object and increase the count
      # We access objects from a Hash via its key, the String
      w = words[s]
      # Which book am I loading?
      if (filename == DRACULA)
        w.increment_dracula
      elsif (filename == FRANKENSTEIN)
        w.increment_franken
      end
    else
      # Otherwise make a new word
      w = Word.new(s)
      # And add entry to the Hash 
      # The key for us is the String and the value is the Word object
      words[s] = w
      if (filename == DRACULA)
        w.increment_dracula
      elsif (filename == FRANKENSTEIN)
        w.increment_franken
      end
    end
  end
end

Here is the display:-

Thursday 14 March 2013

Translating a Word counting sketch to ruby-processing

Here is a sketch that was based on a prototype by Daniel Schiffman to illustrate the use of the processing HashInt class, I can't see the HashInt class catching on really, particularly not for rubyists where alternative options abound. Anyway I liked the sketch and I think sometimes less is more, in later versions Schiffman sorts the words according to their frequency (to show off IntClass functionality), however to my mind it spoils the sketch!
class Word
  # A helper to keep track of word frequency.
  attr_accessor :word
  attr_reader :count
  def initialize word
    @word = word
    @count = 1
  end

  def increment
    @count += 1
  end
end

################################
# This sketch is translated from a vanilla processing sketch by Daniel Schiffman
# that was designed to demonstrate the use IntHash class in vanilla processing.
# Similar results can easily be obtained using more idiomatic ruby. Here IntHash 
# has been replaced by a String => Word hash (as used in a Schiffman prototype). 
# Read about concordance here:-
# http://en.wikipedia.org/wiki/Concordance_(publishing)
################################

attr_reader :concordance, :lines, :tokens, :counter

def setup
  size 640, 360
  @counter = 0
  @concordance = {}
  # Open a file, read its contents, and 'scan' for words using a regex.
  # Include words with apostrophe eg Harker's (alt. source 'hamlet.txt')
  @tokens = File.read(data_path("dracula.txt")).scan(/[\w'-]+/)
  text_font(create_font("Georgia", 24))
end

def draw
  background 51
  fill 255
  s = (tokens[counter] == "I")? tokens[counter] : tokens[counter].downcase
  @counter = (counter + 1) % tokens.length
  if (concordance.has_key?(s))
    # Get the word object and increase the count
    # We access objects from a Hash via its key, the String
    w = concordance[s]
    w.increment    # increment word count
  else
    # Otherwise make a new Word instance and add it to 
    # the Hash using the word String as the key
    concordance[s] = Word.new(s)
  end

  # x and y will be used to locate each word
  x = 0
  y = height - 10

  # Look at each word
  concordance.values.each do |w|
    # Only display words that appear 3 times
    if (w.count > 3)  # access word count
      # The size is the count
      fsize = constrain(w.count, 0, 100)
      text_size(fsize)
      text(w.word, x, y)
      # Move along the x-axis
      x += text_width(w.word) + 1
    end

    # If x gets to the end, move y
    # If y == 0 we are done
    if (y == 0)
      no_loop
    else
      if (x > width)
        x = 0
        y = (y < 0)? 0 : y - 100
      end
    end
  end
end

Wednesday 13 March 2013

PeasyCam for processing-2.0 under the Hood

Now more interestingly lets take a look at PeasyCam (an unofficial update for processing-2.0 that uses reflection to registerMethod("mouseEvent", this) here we can see lots of "access" methods, this must be when java reflection is used:-
puts cam.class.methods

access$2600
access$2800
access$2000
access$100
access$1402
java_send
access$1000
access$2400
access$600
access$1800
access$500
access$900
access$2500
[]
apply
access$600?
access$2200
access$700
access$800
access$1500
access$2300
access$000
access$1302
access$1400
access$1500?
access$300
access$1700
access$2700
access$1200
java_method
access$1600
access$2302
access$400?
access$200
access$1900
access$1300
access$2102
access$1100
access$1002
access$400
access$000?
inherited
new
singleton_class
java_class
field_writer
new_array
__persistent__=
__persistent__
field_accessor
field_reader
java_class=
yaml_tag
allocate
superclass
const_get
private_constant
public_instance_methods
autoload?
freeze
class_variable_get
const_missing
included_modules
==
class_exec
psych_yaml_as
public_class_method
ancestors
instance_method
<
yaml_as
protected_method_defined?
>
===
private_instance_methods
hash
class_variables
public_constant
<=
method_defined?
instance_methods
class_variable_defined?
name
private_method_defined?
const_set
autoload
include?
protected_instance_methods
module_exec
module_eval
<=>
constants
private_class_method
class_variable_set
to_s
public_method_defined?
class_eval
>=
remove_class_variable
const_defined?
handle_different_imports
psych_to_yaml
include_class
to_yaml
java_kind_of?
to_yaml_properties
java_signature
methods
define_singleton_method
initialize_clone
extend
nil?
tainted?
method
is_a?
instance_variable_defined?
instance_variable_get
instance_variable_set
public_method
display
send
private_methods
enum_for
com
to_java
public_send
instance_of?
taint
class
java_annotation
instance_variables
!~
org
untrust
=~
protected_methods
trust
inspect
java_implements
tap
frozen?
initialize_dup
java
respond_to?
java_package
untaint
respond_to_missing?
clone
java_name
to_enum
singleton_methods
untrusted?
eql?
kind_of?
dup
java_require
javax
public_methods
instance_exec
__send__
instance_eval
equal?
object_id
__id__
!
!=

ArcBall library example under the Hood

Here is my simple ruby-processing example under the hood
puts self.inspect
#<Processing::App::Sketch>
puts self.class.ancestors

Sketch
Processing::App
Processing::HelperMethods
Math
Java::ProcessingCore::PApplet
Java::ProcessingCore::PConstants
Java::JavaLang::Runnable
Java::JavaAwtEvent::MouseListener
Java::JavaAwtEvent::MouseWheelListener
Java::JavaAwtEvent::MouseMotionListener
Java::JavaAwtEvent::KeyListener
Java::JavaAwtEvent::FocusListener
Java::JavaUtil::EventListener
Java::JavaApplet::Applet
Java::JavaAwt::Panel
Java::JavaxAccessibility::Accessible
Java::JavaAwt::Container
Java::JavaAwt::Component
Java::JavaAwtImage::ImageObserver
Java::JavaAwt::MenuContainer
Java::JavaIo::Serializable
Java::JavaLang::Object
ConcreteJavaProxy
JavaProxy
JavaProxyMethods
Object
Kernel
BasicObject

puts self.class.methods

java_interfaces
java_proxy_class=
const_missing
java_proxy_class
library_loaded?
has_slider
method_added
sketch_class
load_java_library
inherited
load_library
load_ruby_library
load_libraries
full_screen
url_encode
show_depth_warning_xyz
show_method_warning
java_send
dist
exec
splice
main
print
blendColor
createInput
load_strings
split
parse_int
nf
lerp
run_sketch
show_missing_warning
reverse
createReader
lerp_color
lerpColor
acos
sort
floor
sqrt
parseInt
nfs
hour
atan
get_extension
unhex
selectOutput
min
nfp
str
match_pattern
trim
create_path
unbinary
nfc
url_decode
parseFloat
urlDecode
expand
debug
match
urlEncode
round
minute
arraycopy
cos
tan
radians
ceil
atan2
parseChar
save_bytes
matchAll
loadStrings
parse_char
match_all
create_writer
array_copy
append
create_reader
parse_byte
month
map
parse_float
degrees
saveStream
println
asin
show_variation_warning
save_stream
select_impl
showDepthWarningXYZ
shorten
exp
select_input
join
saveBytes
createOutput
max
second
createPath
getExtension
save_strings
parseByte
sq
select_folder
showVariationWarning
binary
loadBytes
create_input
blend_color
save_stream?
hex
subset
norm
log
parseBoolean
open
parse_boolean?
year
mag
show_depth_warning
showMissingWarning
selectFolder
day
selectImpl
matchPattern
split_tokens
access$000
showMethodWarning
sin
arrayCopy
splitTokens
select_output
java_method
saveStrings
parse_boolean
constrain
runSketch
pow
selectInput
create_output
abs
concat
createWriter
showDepthWarning
load_bytes
newAudioClip
new_audio_clip
access$100
access$500
access$002
instance_of
access$500?
is_instance_of
request_focus_controller=
set_request_focus_controller
is_instance_of?
instanceOf
access$400
setRequestFocusController
isInstanceOf
requestFocusController=
new
singleton_class
java_class
field_writer
new_array
__persistent__=
__persistent__
[]
field_accessor
field_reader
java_class=
yaml_tag
allocate
superclass
const_get
private_constant
public_instance_methods
autoload?
freeze
class_variable_get
included_modules
==
class_exec
psych_yaml_as
public_class_method
ancestors
instance_method
<
yaml_as
protected_method_defined?
>
===
private_instance_methods
hash
class_variables
public_constant
<=
method_defined?
instance_methods
class_variable_defined?
name
private_method_defined?
const_set
autoload
include?
protected_instance_methods
module_exec
module_eval
<=>
constants
private_class_method
class_variable_set
to_s
public_method_defined?
class_eval
>=
remove_class_variable
const_defined?
handle_different_imports
psych_to_yaml
include_class
to_yaml
java_kind_of?
to_yaml_properties
java_signature
methods
define_singleton_method
initialize_clone
extend
nil?
tainted?
method
is_a?
instance_variable_defined?
instance_variable_get
instance_variable_set
public_method
display
send
private_methods
enum_for
com
to_java
public_send
instance_of?
taint
class
java_annotation
instance_variables
!~
org
untrust
=~
protected_methods
trust
inspect
java_implements
tap
frozen?
initialize_dup
java
respond_to?
java_package
untaint
respond_to_missing?
clone
java_name
to_enum
singleton_methods
untrusted?
eql?
kind_of?
dup
java_require
javax
public_methods
instance_exec
__send__
instance_eval
equal?
object_id
__id__
!
!=

Tuesday 12 March 2013

Ruby Processing under the Hood

Here is some under the hood analysis of the "array.rb" sample.
puts self.inspect
 
#<Processing::App::Sketch>
puts self.class.ancestors

Sketch
Processing::App
Processing::HelperMethods
Math
Java::ProcessingCore::PApplet
Java::ProcessingCore::PConstants
Java::JavaLang::Runnable
Java::JavaAwtEvent::MouseListener
Java::JavaAwtEvent::MouseWheelListener
Java::JavaAwtEvent::MouseMotionListener
Java::JavaAwtEvent::KeyListener
Java::JavaAwtEvent::FocusListener
Java::JavaUtil::EventListener
Java::JavaApplet::Applet
Java::JavaAwt::Panel
Java::JavaxAccessibility::Accessible
Java::JavaAwt::Container
Java::JavaAwt::Component
Java::JavaAwtImage::ImageObserver
Java::JavaAwt::MenuContainer
Java::JavaIo::Serializable
Java::JavaLang::Object
ConcreteJavaProxy
JavaProxy
JavaProxyMethods
Object
Kernel
BasicObject

puts self.class.methods

java_proxy_class=
java_proxy_class
java_interfaces
library_loaded?
has_slider
method_added
sketch_class
load_java_library
inherited
load_library
load_ruby_library
load_libraries
full_screen
url_encode
show_depth_warning_xyz
show_method_warning
java_send
dist
exec
splice
main
print
blendColor
createInput
load_strings
split
parse_int
nf
lerp
run_sketch
show_missing_warning
reverse
createReader
lerp_color
lerpColor
acos
sort
floor
sqrt
parseInt
nfs
hour
atan
get_extension
unhex
selectOutput
min
nfp
str
match_pattern
trim
create_path
unbinary
nfc
url_decode
parseFloat
urlDecode
expand
debug
match
urlEncode
round
minute
arraycopy
cos
tan
radians
ceil
atan2
parseChar
save_bytes
matchAll
loadStrings
parse_char
match_all
create_writer
array_copy
append
create_reader
parse_byte
month
map
parse_float
degrees
saveStream
println
asin
show_variation_warning
save_stream
select_impl
showDepthWarningXYZ
shorten
exp
select_input
join
saveBytes
createOutput
max
second
createPath
getExtension
save_strings
parseByte
sq
select_folder
showVariationWarning
binary
loadBytes
create_input
blend_color
save_stream?
hex
subset
norm
log
parseBoolean
open
parse_boolean?
year
mag
show_depth_warning
showMissingWarning
selectFolder
day
selectImpl
matchPattern
split_tokens
access$000
showMethodWarning
sin
arrayCopy
splitTokens
select_output
java_method
saveStrings
parse_boolean
constrain
runSketch
pow
selectInput
create_output
abs
concat
createWriter
showDepthWarning
load_bytes
newAudioClip
new_audio_clip
access$100
access$500
access$002
instance_of
access$500?
is_instance_of
request_focus_controller=
set_request_focus_controller
is_instance_of?
instanceOf
access$400
setRequestFocusController
isInstanceOf
requestFocusController=
new
singleton_class
java_class
field_writer
new_array
__persistent__=
__persistent__
[]
field_accessor
field_reader
java_class=
yaml_tag
allocate
superclass
const_get
private_constant
public_instance_methods
autoload?
freeze
class_variable_get
const_missing
included_modules
==
class_exec
psych_yaml_as
public_class_method
ancestors
instance_method
<
yaml_as
protected_method_defined?
>
===
private_instance_methods
hash
class_variables
public_constant
<=
method_defined?
instance_methods
class_variable_defined?
name
private_method_defined?
const_set
autoload
include?
protected_instance_methods
module_exec
module_eval
<=>
constants
private_class_method
class_variable_set
to_s
public_method_defined?
class_eval
>=
remove_class_variable
const_defined?
handle_different_imports
psych_to_yaml
include_class
to_yaml
java_kind_of?
to_yaml_properties
java_signature
methods
define_singleton_method
initialize_clone
extend
nil?
tainted?
method
is_a?
instance_variable_defined?
instance_variable_get
instance_variable_set
public_method
display
send
private_methods
enum_for
com
to_java
public_send
instance_of?
taint
class
java_annotation
instance_variables
!~
org
untrust
=~
protected_methods
trust
inspect
java_implements
tap
frozen?
initialize_dup
java
respond_to?
java_package
untaint
respond_to_missing?
clone
java_name
to_enum
singleton_methods
untrusted?
eql?
kind_of?
dup
java_require
javax
public_methods
instance_exec
__send__
instance_eval
equal?
object_id
__id__
!
!=

Thursday 7 March 2013

Stochastic Menger using PShape api

NB: This sketch requires the development version of ruby-processing see here.

##########################
# StochasticMenger.rb
# (processing-2.0)
# author Martin Prout
##########################

load_library 'arcball'
import "arcball"

PTS = [-1, 0, 1]
MIN_SIZE = 50
X = 0
Y = 1
Z = 2

attr_reader :arcball, :menger

def setup
  size(640, 480, P3D)
  smooth(8)
  camera
  camera(width/2.0, height/2.0, (height/2.0) / tan(PI*30.0 / 180.0), 0, 0, 0, 0, 1, 0)
  @arcball = ArcBall.new(0, 0, min(width - 20, height - 20) * 0.8)
  @menger = create_shape(GROUP)
  create_menger(0, 0, 0, height * 0.8)
end

def draw
  background(40, 40, 180)
  no_stroke
  lights
  update
  define_lights
  render
end

def render
  menger.set_ambient(50)
  menger.set_specular(150)
  shape(menger)
end

def create_menger(xx, yy, zz, sz)
  u = sz / 3.0
  if (sz < MIN_SIZE) # recursion limited by minimum cube size
    no_stroke
    menger.add_child(create_cube(xx, yy, zz, sz)) # create and add a cube  
  else
    PTS.each do |i|
      PTS.each do |j|
        PTS.each do |k|
          if ((i.abs + j.abs + k.abs) > 1)
            create_menger(xx + (i * u), yy + (j * u), zz + (k * u), u)
          end
        end
      end
    end
  end
end

def create_cube(xx, yy, zz, sz)
  dim = sz / 2.0
  chance = rand
  if chance < 0.8
    cube = create_shape(BOX, sz, sz, sz)
    cube.set_fill(color(0, 0, 219))
  else
    cube = create_shape(SPHERE, dim)
    cube.set_fill(color(224, 223, 219))
  end
  cube.translate(xx, yy, zz)
  return cube
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 define_lights
  ambient_light(50, 50, 50)
  point_light(30, 30, 30, 200, 240, 0)
  directional_light(50, 50, 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 '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


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