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

Showing posts with label ruby processing. Show all posts
Showing posts with label ruby processing. Show all posts

Saturday, 5 October 2013

A custom Vector library for ruby processing

I recently got my copy of Practical Object Oriented Design in Ruby by Sandi Metz book, this got me thinking what could I could apply the ideas to in ruby-processing, and here is an early crack at it. Creating a custom (pure-ruby) vector library which can replace the hybrid RPVector (that extends PVector from processing) class of a previous post. This is very much a first crack (but it works, only difference needed is load_library :vec and use normalize! instead of normalize, much more ruby like I think) because I have ideas for extending the functionality along some of these lines, however my sentiment is with toxi re simple made easy. Further if I use vanilla processing logic the cross_product method (modulus, dist etc) could all live in Vec, but I can easily defer that decision. I haven't at this stage made use distance_squared externally, however this will be more efficient for testing boundary conditions of say a bouncing ball than regular dist. Not shown here are the rspec tests that I've been using to ensure the code behaves, I am tempted to bundle this and a Quaternion class and possibly some others as a core ruby-processing library, since the operations of PVector are not at all ruby like, and many more people seem to come to ruby-processing from ruby, not the other way round sadly.
class Vec
  attr_accessor :x, :y, :z
  EPSILON = 9.999999747378752e-05     # a value used by processing.org
  def initialize(x = 0 ,y = 0, z = 0)
    @x, @y, @z = x, y, z
    post_initialize
  end

  def post_initialize
    nil
  end

  def ==(vec)
    (x - vec.x).abs < EPSILON && (y - vec.y).abs < EPSILON && (z - vec.z).abs < EPSILON
  end
end


class Vec2D < Vec

  # Modulus of vec. Also known as length, size or norm
  def modulus
    Math.hypot(x, y)
  end

  def self.dist_squared(vec_a, vec_b)
    (vec_a.x - vec_b.x)**2 + (vec_a.y - vec_b.y)**2
  end

  def self.dist(vec_a, vec_b)
    Math.hypot(vec_a.x - vec_b.x, vec_a.y - vec_b.y)
  end

  # vanilla processing returns a Vector, rather than Scalar (defaults to 3D result when z = 0)
  def cross_product(vec)
    x * vec.y - y * vec.x
  end

  # Scalar product, also known as inner product or dot product
  def dot(vec)
    x * vec.x + y * vec.y
  end

  def collinear_with?(vec)
    cross_product(vec).abs < EPSILON
  end

  def +(vec)
    Vec2D.new(x + vec.x, y + vec.y)
  end

  def -(vec)
    Vec2D.new(x - vec.x, y - vec.y)
  end

  def *(scalar)
    Vec2D.new(x * scalar, y * scalar)
  end

  def / (scalar)
    Vec2D.new(x / scalar, y / scalar) unless scalar == 0
  end

  def normalize!
    @x, @y = x / modulus, y / modulus
    return self
  end

  alias :mag :modulus

end

class Vec3D < Vec

  def modulus
    Math.sqrt(x**2 + y**2 + z**2)
  end

  def self.dist_squared(vec_a, vec_b)
    (vec_a.x - vec_b.x)**2 + (vec_a.y - vec_b.y)**2 + (vec_a.z - vec_b.z)**2
  end

  def self.dist(vec_a, vec_b)
    Math.sqrt(self.dist_squared(vec_a, vec_b))
  end


  def cross_product(vec)
    xc = y * vec.z - z * vec.y
    yc = z * vec.x - x * vec.z
    zc = x * vec.y - y * vec.x
    Vec3D.new(xc, yc, zc)
  end

  # Scalar product, also known as inner product or dot product
  def dot(vec)
    x * vec.x + y * vec.y + z * vec.z
  end

  def collinear_with?(vec)
    cross_product(vec) == Vec3D.new
  end

  def +(vec)
    Vec3D.new(x + vec.x, y + vec.y, z + vec.z)
  end

  def -(vec)
    Vec3D.new(x - vec.x, y - vec.y, z - vec.z)
  end

  def * (scalar)
    Vec3D.new(x * scalar, y * scalar, z * scalar)
  end

  def / (scalar)
    Vec3D.new(x / scalar, y / scalar, z / scalar) unless scalar.abs < EPSILON
  end

  def normalize!
    @x, @y, @z = x / modulus, y / modulus, z / modulus
    return self
  end

  alias :mag :modulus
end

A Little Test courtesy of Daniel Shiffman
#
# Vector 
# by Daniel Shiffman.  
# 
# Demonstration some basic vector math: subtraction, normalization, scaling
# Normalizing a vector sets its length to 1.
#
load_library :vec

def setup
  size(640,360)
end

def draw
  background(0)

  # A vector that points to the mouse location
  mouse = Vec2D.new(mouse_x, mouse_y)
  # A vector that points to the center of the window
  center = Vec2D.new(width/2,height/2)
  # Subtract center from mouse which results in a vector that points from center to mouse
  mouse = mouse - center # note need assign result to mouse

  # Normalize the vector the ! means we are changing the value of mouse
  mouse.normalize!

  # Multiply its length by 150 (Scaling its length)
  mouse = mouse * 150 # note need assign the result to mouse

  translate(width/2,height/2)
  # Draw the resulting vector
  stroke(255)
  stroke_weight(4)
  line(0, 0, mouse.x, mouse.y)
end

Now interestingly the following also works so you can use the
+=, -=, /=, and *= assignment methods ( but you cannot override += etc as a methods unlike C++ )
So what this means is that in ruby += etc are just syntactic shortcuts, and not an operator in its own right (which is probably a good thing).
#
# Vector 
# by Daniel Shiffman.  
# 
# Demonstration some basic vector math: subtraction, normalization, scaling
# Normalizing a vector sets its length to 1.
#
load_library :vec

def setup
  size(640,360)
end

def draw
  background(0)

  # A vector that points to the mouse location
  mouse = Vec2D.new(mouse_x, mouse_y)
  # A vector that points to the center of the window
  center = Vec2D.new(width/2,height/2)
  # Subtract center from mouse which results in a vector that points from center to mouse
  mouse -= center  # note we can assign result to mouse using -=

  # Normalize the vector the ! means we are changing the value of mouse
  mouse.normalize!

  # Multiply its length by 150 (Scaling its length)
  mouse *= 150 # note we can assign result to mouse using *=

  translate(width/2,height/2)
  # Draw the resulting vector
  stroke(255)
  stroke_weight(4)
  line(0, 0, mouse.x, mouse.y)

end

Wednesday, 5 June 2013

Ditching jruby-complete, what is holding me back?

Well it is only the glsl shader sketches (I think). So something is added by including the jruby-complete.jar classpath in the command line that allows these sketches to run. So one area of experimentation is to try to add similar classpath when firing up ruby-processing using jruby, rather than from java.

-J-cp="${RP5_ROOT}/lib/core/"

Should ensure processing core and jogl/gluegen run-times get included, but something more seems to be required...
Something that enables compiling of glsl shaders?

Useful Bookmarks?

  1. http://www.compoundtheory.com/
  2. https://github.com/markmandel/jruby-lwjgl
  3. http://codeanticode.wordpress.com/tag/glsl/
  4. http://arcsynthesis.org/gltut/ 
Hey radical thought, since jruby-lwgl seems to work really well, time to ditch ruby-processing, and try something new for a bit at least?

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

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:-

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__
!
!=

Monday, 24 December 2012

Rotating Arcs Sketch in Ruby Processing

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

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

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

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

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

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

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

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

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

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


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


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

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

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

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

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

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

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

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

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

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

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

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

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


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


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

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

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

def mouse_pressed
  @dosave = true
end

Wednesday, 19 December 2012

Exporting cf3ruby directly to 'large' pdf

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

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


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


def draw
  exit
end


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

Tuesday, 4 December 2012

A 3D-texture example with my hacked ruby-processing

To get ruby-processing working with processing-2.0b6 core (and the associated opengl/jogl) required a number of hacks. One of which seemed to lose the ability to run sketches that were explicitly sub-classed from Processing::App, but that is probably no bad thing. Here is the modified textured_cube example, note NORMALIZED is replace by NORMAL in processing-2.0 syntax:-


# TexturedCube
# by Dave Bollinger.
# 
# Drag mouse to rotate cube. Demonstrates use of u/v coords in 
# vertex and effect on texture().


def setup
  size 649, 360, P3D
  @rotx = PI/4
  @roty = PI/4
  @tex = load_image "berlin-1.jpg"
  texture_mode NORMAL  # Changed for processing 2.0
  fill 255
  stroke 44, 48, 32
end

def draw
  background 0
  no_stroke
  translate width/2, height/2, -100
  rotate_x @rotx
  rotate_y @roty
  scale 90
  texture_cube
end

def texture_cube

# Given one texture and six faces, we can easily set up the uv coordinates
# such that four of the faces tile "perfectly" along either u or v, but the other
# two faces cannot be so aligned.  This code tiles "along" u, "around" the X/Z faces
# and fudges the Y faces - the Y faces are arbitrarily aligned such that a
# rotation along the X axis will put the "top" of either texture at the "top"
# of the screen, but is not otherwised aligned with the X/Z faces. (This
# just affects what type of symmetry is required if you need seamless
# tiling all the way around the cube)

begin_shape QUADS

  texture @tex

  # +Z "front" face
  vertex -1, -1,  1, 0, 0
  vertex  1, -1,  1, 1, 0
  vertex  1,  1,  1, 1, 1
  vertex -1,  1,  1, 0, 1

  # -Z "back" face
  vertex  1, -1, -1, 0, 0
  vertex -1, -1, -1, 1, 0
  vertex -1,  1, -1, 1, 1
  vertex  1,  1, -1, 0, 1

  # +Y "bottom" face
  vertex -1,  1,  1, 0, 0
  vertex  1,  1,  1, 1, 0
  vertex  1,  1, -1, 1, 1
  vertex -1,  1, -1, 0, 1

  # -Y "top" face
  vertex -1, -1, -1, 0, 0
  vertex  1, -1, -1, 1, 0
  vertex  1, -1,  1, 1, 1
  vertex -1, -1,  1, 0, 1

  # +X "right" face
  vertex  1, -1,  1, 0, 0
  vertex  1, -1, -1, 1, 0
  vertex  1,  1, -1, 1, 1
  vertex  1,  1,  1, 0, 1

  # -X "left" face
  vertex -1, -1, -1, 0, 0
  vertex -1, -1,  1, 1, 0
  vertex -1,  1,  1, 1, 1
  vertex -1,  1, -1, 0, 1

  end_shape
end

def mouse_dragged
  rate = 0.01
  @rotx += ( pmouse_y - mouse_y ) * rate
  @roty += ( mouse_x - pmouse_x ) * rate
end

Tuesday, 28 August 2012

Control Panel Enhanced ArcBall Sketch

Here is an enhanced arcball sketch, features use of control panel for zoom facility, and drop down menu too choose which or whether rotation is frozen about an axis. See previous post for library.
load_libraries 'arcball', 'opengl', 'control_panel'
import "arcball"
import "opengl"

X = 0
Y = 1
Z = 2

attr_reader :my_ball, :zoom

def setup
  size(600, 600, OPENGL)
  setup_opengl
  @my_ball = ArcBall.new(width/2.0, height/2.0, min(width - 20, height - 20) * 0.5)
  control_panel do |c|
    c.title = 'Controller'
    c.slider  :zoom, 0..2.0, 0.0
    c.menu(:freeze, ['X-axis', 'Y-axis', 'Z-axis', 'free'], 'free') {|m| load_menu_item(m) }
  end
  @zoom = 0
end

def draw
  background(50, 50, 100)
  translate(width/2.0, height/2.0, zoom * width/2.0)
  define_lights
  update
  lights
  stroke(0)
  cube(my_ball.radius / 2.0)
end

def setup_opengl
  hint ENABLE_OPENGL_4X_SMOOTH     # optional
  hint DISABLE_OPENGL_ERROR_REPORT # optional
end

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

def mouse_pressed
  my_ball.mouse_pressed(mouse_x, mouse_y)
end

def mouse_dragged
  my_ball.mouse_dragged(mouse_x, mouse_y)
end

def define_lights
  ambient(20, 20, 20)
  ambient_light(50, 50, 50)
  point_light(30, 30, 30, 200, -150, 0)
  directional_light(0, 30, 50, 1, 0, 0)
  spot_light(30, 30, 30, 0, 40, 200, 0, -0.5, -0.5, PI / 2, 2)
end

def load_menu_item(m)
  case(m)
  when 'X-axis':
    my_ball.select_axis(X)
  when 'Y-axis':
    my_ball.select_axis(Y)
  when 'Z-axis':
    my_ball.select_axis(Z)
  when 'free'
    my_ball.select_axis(-1)
  end
end

def cube(sz)
  sz *= 0.5
  fill(200,  200,  200,  255)
  begin_shape(QUADS)
    vertex(-sz, -sz, -sz)
    vertex(+sz, -sz, -sz)
    vertex(+sz, +sz, -sz)
    vertex(-sz, +sz, -sz)
    vertex(-sz, -sz, +sz)
    vertex(+sz, -sz, +sz)
    vertex(+sz, +sz, +sz)
    vertex(-sz, +sz, +sz)
    vertex(-sz, -sz, -sz)
    vertex(-sz, -sz, +sz)
    vertex(-sz, +sz, +sz)
    vertex(-sz, +sz, -sz)
    vertex(+sz, -sz, -sz)
    vertex(+sz, -sz, +sz)
    vertex(+sz, +sz, +sz)
    vertex(+sz, +sz, -sz)
    vertex(-sz, -sz, -sz)
    vertex(+sz, -sz, -sz)
    vertex(+sz, -sz, +sz)
    vertex(-sz, -sz, +sz)
    vertex(-sz, +sz, -sz)
    vertex(+sz, +sz, -sz)
    vertex(+sz, +sz, +sz)
    vertex(-sz, +sz, +sz)
    end_shape
end

sketch screenshot using the gimp

Monday, 27 August 2012

A ArcBall library for ruby-processing

Here is my library, NB: three classes in a file named arcball.rb, and in a folder arcball nested in a library folder (NB: AVector is a low fat version of PVector for use here). Note you can put other ruby libraries in that folder. Note the use of :: separators and the Java prefix to call the EPSILON constant from vanilla processing.
class ArcBall
  attr_reader :center_x, :center_y, :v_down, :v_drag, :q_now, :q_drag, :q_down, :axis, :axis_set, :radius

  def initialize(cx, cy, radius)
    @center_x = cx
    @center_y = cy
    @radius = radius
    @v_down = AVector.new
    @v_drag = AVector.new
    @q_now = Quaternion.new
    @q_down = Quaternion.new
    @q_drag = Quaternion.new
    @axis_set = [AVector.new(1.0, 0.0, 0.0), AVector.new(0.0, 1.0, 0.0), AVector.new(0.0, 0.0, 1.0)]
    @axis = -1
  end

  def select_axis(axis)
    @axis = axis
  end

  def mouse2sphere(x, y)
    v = AVector.new((x - center_x) / radius, (y - center_y) / radius, 0)
    mag = v.x * v.x + v.y * v.y
    if (mag > 1.0)
      v.normalize
    else
      v.z = Math.sqrt(1.0 - mag)
    end
    v = constrain(v, axis_set[axis]) unless (axis == -1)
    return v
  end

  def mouse_pressed(x, y)
    @v_down = mouse2sphere(x, y)
    @q_down.copy(q_now)
    @q_drag.reset
  end

  def mouse_dragged(x, y)
    @v_drag = mouse2sphere(x, y)
    @q_drag.set(AVector.dot(v_down, v_drag), v_down.cross(v_drag))
  end


  def constrain(vector, axis)
    res = AVector.sub(vector, AVector.mult(axis, AVector.dot(axis, vector)))
    res.normalize
  end

  def update
    @q_now = Quaternion.mult(q_drag, q_down)
    quat2matrix(q_now)
  end

  def quat2matrix(q)
    q.get_value
  end
end

class Quaternion
  attr_reader :w, :x, :y, :z

  def initialize(w = 1.0,  x = 0,  y = 0,  z = 0)
    @w, @x, @y, @z = w,  x,  y,  z
  end

  def reset
    @w = 1.0
    @x = 0.0
    @y = 0.0
    @z = 0.0
  end

  def set(w, v)
    @w = w
    @x = v.x
    @y = v.y
    @z = v.z
  end

  def copy(q)
    @w = q.w
    @x = q.x
    @y = q.y
    @z = q.z
  end

  def self.mult(q1, q2)      # class method   
    x0 = q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y
    y0 = q1.w * q2.y + q1.y * q2.w + q1.z * q2.x - q1.x * q2.z
    z0 = q1.w * q2.z + q1.z * q2.w + q1.x * q2.y - q1.y * q2.x
    w0 = q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z
    Quaternion.new(w0,  x0,  y0,  z0)
  end

  def get_value
    sa = Math.sqrt(1.0 - w * w)
    sa = 1.0 unless (sa >= Java::processing::core::PConstants::EPSILON)
    [Math.acos(w) * 2, x / sa, y / sa, z / sa]
  end
end

class AVector

  attr_accessor :x, :y, :z

  def initialize(x = 0, y = 0, z = 0)
    @x, @y, @z = x, y, z
  end

  def add(vector)
    AVector.new(vector.x + x, vector.y + y, vector.z + z)
  end

  def normalize
    orig_dist = Math.sqrt(x * x + y * y + z * z)
    @x /= orig_dist
    @y /= orig_dist
    @z /= orig_dist
    self
  end

  def self.dot(v1, v2)
    v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
  end

  def self.mult(v, scalar)
    AVector.new(v.x * scalar, v.y * scalar, v.z * scalar)
  end

  def self.sub(v1, v2)
    AVector.new(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z)
  end

  def cross(v)
    AVector.new(y * v.z - v.y * z,  z * v.x - v.z * x, x * v.y - v.x * y)
  end

end

This is the test class test_arcball.rb:-
load_libraries 'arcball', 'opengl'
import "arcball"
import "opengl"

X = 0
Y = 1
Z = 2

attr_reader :my_ball

def setup
  size(600, 600, OPENGL)
  setup_opengl
  @my_ball = ArcBall.new(width/2.0, height/2.0, min(width - 20, height - 20) * 0.5)
end

def draw
  background(50, 50, 100)
  translate(width/2.0, height/2.0)  # @todo add zoom via z, using control_panel
  define_lights
  update
  lights
  stroke(0)
  cube(my_ball.radius)
end

def setup_opengl
  hint ENABLE_OPENGL_4X_SMOOTH     # optional
  hint DISABLE_OPENGL_ERROR_REPORT # optional
end

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

def mouse_pressed
  my_ball.mouse_pressed(mouse_x, mouse_y)
end

def mouse_dragged
  my_ball.mouse_dragged(mouse_x, mouse_y)
end

def define_lights
  ambient(20, 20, 20)
  ambient_light(50, 50, 50)
  point_light(30, 30, 30, 200, -150, 0)
  directional_light(0, 30, 50, 1, 0, 0)
  spot_light(30, 30, 30, 0, 40, 200, 0, -0.5, -0.5, PI / 2, 2)
end

def key_pressed                # @todo select via control_panel instead
  case(key)
  when 'x':
    my_ball.select_axis(X)
  when 'y':
    my_ball.select_axis(Y)
  when 'z':
    my_ball.select_axis(Z)
  end
end

def key_released
  my_ball.select_axis(-1)
end

def cube(sz)
  sz *= 0.5
  fill(200,  200,  200,  255)
  begin_shape(QUADS)
    vertex(-sz, -sz, -sz)
    vertex(+sz, -sz, -sz)
    vertex(+sz, +sz, -sz)
    vertex(-sz, +sz, -sz)
    vertex(-sz, -sz, +sz)
    vertex(+sz, -sz, +sz)
    vertex(+sz, +sz, +sz)
    vertex(-sz, +sz, +sz)
    vertex(-sz, -sz, -sz)
    vertex(-sz, -sz, +sz)
    vertex(-sz, +sz, +sz)
    vertex(-sz, +sz, -sz)
    vertex(+sz, -sz, -sz)
    vertex(+sz, -sz, +sz)
    vertex(+sz, +sz, +sz)
    vertex(+sz, +sz, -sz)
    vertex(-sz, -sz, -sz)
    vertex(+sz, -sz, -sz)
    vertex(+sz, -sz, +sz)
    vertex(-sz, -sz, +sz)
    vertex(-sz, +sz, -sz)
    vertex(+sz, +sz, -sz)
    vertex(+sz, +sz, +sz)
    vertex(-sz, +sz, +sz)
    end_shape
end



Sunday, 17 June 2012

A Gui for my pixelation library

Over on my other blog I recently described an application for pixelating pictures in processing, but also using the cfdg program (CF3), here is the program adapted for ruby-processing, with an added gui using "control_panel"




load_libraries 'pde2cfdg', 'control_panel'
import "pde2cfdg"

#
# Two cfdg files are written to the sketch folder one for design one for data,
# this means it easier to edit the design (eg to replace CIRCLE with a custom shape)
# The output png file is written to the sketch folder,
#

attr_reader :cfdg, :dot_size

def setup
    size(640, 512)
    background(255)
    setup_panel
    colorMode(HSB, 1.0)
    @cfdg = ProcessingToCF.new(self)
    cfdg.set_dot_size(dot_size)
    cfdg.setPathToCFDG("/home/tux/CF3/cfdg")
    cfdg.get_input
    cfdg.writeCFDG
end

def setup_panel
    control_panel do |c|
      c.title = "Control:"
      c.slider(:dot_size, 1..6, 3)
      c.button(:re_run)
    end
end

def re_run
  cfdg.set_dot_size(dot_size)
  cfdg.writeCFDG
end

def draw
  if (cfdg.finished)
    img = load_image(cfdg.outFile)
    image(img, 0, 0, img.width, img.height)
  end
end
Here's a screenshot of application in action, the artist is Phil Sutton RA

Saturday, 4 February 2012

Using the Control Panel With Toxiclibs

Here is a recent sketch modified to use the ruby processing control panel (doesn't want to work with opengl with current release), next obvious variant to include sliders for resolution and or upper and lower boundary levels.
#
# This example implements a custom VolumetricSpace using an implicit function
# to calculate each voxel. This is slower than the default array or HashMap
# based implementations, but also has much less memory requirements and so might
# be an interesting and more viable approach for very highres voxel spaces
# (e.g. >32 million voxels). This implementation here also demonstrates how to
# achieve an upper boundary on the iso value (in addition to the one given and
# acting as lower threshold when computing the iso surface)
#
# Copyright (c) 2010 Karsten Schmidt & ruby-processing version Martin Prout 2012
# This sketch relies on a custom toxiclibscore library for PovRAY export, and
# features the ruby-processing control panel (doesn't seem to work with opengl)
# This 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
#

load_libraries 'toxiclibscore', 'toxiclibs_p5', 'volumeutils', 'control_panel'
include_package 'toxi.geom'
include_package 'toxi.geom.mesh'
include_package 'toxi.volume'
include_package 'toxi.processing'

RES = 64
ISO = 0.2
MAX_ISO = 0.66

attr_reader :mesh, :gfx, :is_wire_frame, :curr_zoom

def setup()
  size(720,720, P3D)
  @curr_zoom = 1.0
  control_panel do |c|
    c.title = "Implicit Function"
    c.slider :curr_zoom, 0.5..4.0, 1.0
    c.button :wire_frame_view
    c.button :smooth
    c.button :save_sketch_view
    c.button :export_to_povray
    c.button :exit!
  end
  @gfx = ToxiclibsSupport.new(self)
  vol = EvaluatingVolume.new(Vec3D.new(400,400,400), RES, RES, RES, MAX_ISO)
  surface = HashIsoSurface.new(vol)
  @mesh = WETriangleMesh.new()
  surface.compute_surface_mesh(mesh, ISO)
  @is_wire_frame = false
end

def draw()
  background(0)
  translate(width / 2.0, height / 2.0, 0)
  rotate_x(mouse_y * 0.01)
  rotate_y(mouse_x * 0.01)
  scale(curr_zoom)
  if (is_wire_frame)
    no_fill()
    stroke(255)
  else
    fill(255)
    no_stroke()
    lights()
  end
  @gfx.mesh(mesh, true)
end

def wire_frame_view
  @is_wire_frame = !is_wire_frame
end

def smooth
  LaplacianSmooth.new().filter(mesh, 1)
end

def export_to_povray
  file_id = "implicit"
  pw = create_writer(file_id + ".inc")
  mesh.saveAsPOV(pw)
  pw.flush()
  pw.close()
  exit()
end

def save_sketch_view
  save_frame("implicit.png")
end

class EvaluatingVolume < VolumetricSpace
  include Processing::Proxy
  include_package 'toxi.math'
  attr_reader :upper_bound, :lut
  FREQ = PI * 3.8

  def initialize(scal_vec, resX, resY, resZ, upper_limit)
    super(scal_vec, resX, resY, resZ)
    @upper_bound = upper_limit
    @lut=SinCosLUT.new()
  end

  def clear()
    # nothing to do here
  end

  def getVoxelAt(i)
    getVoxel(i % resX, (i % sliceRes) / resX, i / sliceRes)
  end

  def getVoxel(x, y, z)  # can't overload so we renamed
    val = 0
    if (x > 0 && x < resX1 && y > 0 && y < resY1 && z > 0 && z < resZ1)
      xx = x * 1.0 / resX - 0.5  # NB: careful about integer division !!!
      yy = y * 1.0 / resY - 0.5
      zz = z * 1.0 / resZ - 0.5
      #val = lut.sin(xx * FREQ) + lut.cos(yy * FREQ) + lut.sin(zz * FREQ)
      val = lut.sin(xx * FREQ) * (xx * FREQ) + lut.sin(yy * FREQ) * (yy * FREQ) + lut.sin(zz * FREQ) * (zz * FREQ)
      #val = lut.cos(xx * FREQ) * lut.sin(yy* FREQ) + lut.cos(yy* FREQ) * lut.sin(zz* FREQ) + lut.cos(zz* FREQ)* lut.sin(xx* FREQ);
      if (val > upper_bound)
        val = 0
      end
    end
    return val
  end
end
Screenshot of ruby processing sketch

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