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

Saturday 28 September 2013

Refactored Load Save Json Sketch (To isolate responsibilities and use Enumerable)

Here rather than sub-classing Array (which can be unsafe) we extend Enumerable, to make our BubbleData class iteratable (to do this, we only needed to provide the each method). But it also suited us to create a custom add method, so we can check whether the MAX_SIZE has been reached, and if so to "shift" the oldest bubble off the underlying array. We have also isolated OO responsibilities, in that the Sketch class only needs to know about the BubbleData class directly to "load", "display" and "add" bubbles.
# This example demonstrates how easily "sketch data" can be retrieved from a json file
# in ruby-processing. Note this sketch re-uses the Bubble class from the bubble library. 
# The BubbleData class, can load, store and create instances of Bubble (and request them
# to display and/or show their label, when 'mouse over').
# @author Martin Prout, after Daniel Shiffmans version for processing
# 
require "json"

load_library :bubble

attr_reader :bubble_data

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

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

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

class BubbleData
  include Enumerable

  MAX_BUBBLE = 10

  attr_reader :key, :path, :bubbles

  # @param key String for top level hash

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

  def each &block
    bubbles.each &block
  end

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

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

  # @param path to json file

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

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

  private

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

end

Saturday 21 September 2013

Reading and writing RubyStructs to yaml ruby processing

Since ruby 1.9.3 it is possible to read and write RubyStructs to YAML (using Psych), this makes for a more efficient way of storing and restoring data, whilst maintaining human readability see included yaml file. It is essential that the Struct is declared, and this is easiest done in our bubble library (see below).
######################################
# Yet another examples of reading and
# writing to some form of markup,
# appropriatetly yaml using ruby structs 
# by Martin Prout after Dan Shiffman
# ###################################
load_library :bubble

attr_reader :bubbles, :bubble_data


def setup()
  size(640, 360)
  # load data from file
  load_data
end

def draw
  background 255
  bubbles.each do|bubble|
    bubble.display
    bubble.rollover(mouse_x, mouse_y)
  end
end

def load_data
  yaml = Psych.load_file("data/struct_data.yaml")
  # we are storing the data as an array of RubyStruct, in a hash with
  # a symbol as the key (the latter only to show we can, it makes no sense)
  data = yaml[:bubbles]
  @bubbles = []
  # iterate the bubble_data array, and populate the array of bubbles
  data.each do |pt|
    bubbles << Bubble.new(pt.x, pt.y, pt.diameter, pt.label)
  end
end

def save_data
  # demonstrate how easy it is to create yaml object from a hash in ruby
  yaml = bubble_data.to_hash.to_yaml
  # overwite existing 'data.yaml' 
  open("data/struct_data.yaml", 'w') {|f| f.write(yaml) }
end

def mouse_pressed
  # create a new bubble instance, where mouse was clicked
  @bubble_data = BubbleData.new bubbles
  @bubble_data.add_bubble(Bubble.new(mouse_x, mouse_y, rand(40 .. 80), "new label"))
  save_data
  # reload the yaml data from the freshly created file
  load_data
end


class BubbleData
  attr_reader :data
  def initialize data
    @data = data
  end

  def add_bubble bubble
    data << bubble
    if (data.size > 10)
    # Delete the oldest bubble
      data.shift
    end
  end

  # Using symbol as hash key for a change.  Thus demonstrating only to show we can store 
  # more complex data strucures quite readily.
  def to_hash
    {bubbles: data.map{|point| point.to_struct}}
  end

end


The bubble library "library/bubble/bubble.rb"
# The bubble library, include BubbleStruct

class Bubble
  include Processing::Proxy

  attr_reader :x, :y, :diameter, :name, :over

  # Create  the Bubble
  def initialize(x, y, diameter, name)
    @x = x
    @y = y
    @diameter = diameter
    @name = name
    @over = false
  end

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

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

  def to_struct
    BubbleStruct.new(x, y, diameter, name)
  end
end

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

The data file "struct_data.yaml"
---
:bubbles:
- !ruby/struct:BubbleStruct
  x: 160
  y: 103
  diameter: 43.2
  label: Happy
- !ruby/struct:BubbleStruct
  x: 372
  y: 137
  diameter: 52.4
  label: Sad
- !ruby/struct:BubbleStruct
  x: 273
  y: 235
  diameter: 61.0
  label: Joyous
- !ruby/struct:BubbleStruct
  x: 121
  y: 179
  diameter: 44.7
  label: Melancholy

Friday 20 September 2013

Reading and writing to a yaml file ruby-processing

The processing sketch
require "yaml"

attr_reader :bubbles, :bubble_data

def setup()
  size(640, 360)
  # read source_string from file
  load_data
end

def draw
  background 255
  bubbles.each do|bubble|
    bubble.display
    bubble.rollover(mouse_x, mouse_y)
  end
end

def load_data
  yaml = YAML.load_file("data/data.yaml")
  # parse the source string
  @bubble_data = BubbleData.new "bubbles"

  # get the bubble_data from the top level hash
  data = bubble_data.extract_data yaml
  @bubbles = []
  # iterate the bubble_data array, and create an array of bubbles
  data.each do |point|
    bubbles << Bubble.new(
      point["position"]["x"],
      point["position"]["y"],
      point["diameter"],
      point["label"])
  end
end

def save_data
  # demonstrate how easy it is to create json object from a hash in ruby
  yaml = bubble_data.to_hash.to_yaml
  # overwite existing 'data.json' 
  open("data/data.yaml", 'w') {|f| f.write(yaml) }
end

def mouse_pressed
  # create a new bubble instance, where mouse was clicked
  @bubble_data.add_bubble(Bubble.new(mouse_x, mouse_y, rand(40 .. 80), "new label"))
  save_data
  # reload the json data from the freshly created file
  load_data
end

class BubbleData
  attr_reader :name, :data
  def initialize name = "bubbles"
    @name = name
    @data = []
  end

  def add_bubble bubble
    data << bubble
  end

  def extract_data yaml
    @data = yaml[name]
  end

  def to_hash
    {name => data.map{|point| point.to_hash}}
  end

end

class Bubble
  attr_reader :x, :y, :diameter, :name, :over

  def initialize(x, y, diameter, name)
    @x, @y, @diameter, @name = x, y, diameter, name
    @over = false
  end

  def rollover px, py
    d = dist px, py, x, y
    @over = (d < diameter / 2.0)
  end

  def display
    stroke 0
    stroke_weight 2
    no_fill
    ellipse x, y, diameter, diameter
    if over
      fill 0
      text_align CENTER
      text(name, x, y + diameter / 2.0 + 20)
    end
  end

  def to_hash
    {"position" => {"x" => x, "y" => y}, "diameter" => diameter, "label" => name}
  end
end


The data file "data.yaml"
---
bubbles:
- position:
    x: 160
    y: 103
  diameter: 43.19838
  label: Happy
- position:
    x: 372
    y: 137
  diameter: 52.42526
  label: Sad
- position:
    x: 273
    y: 235
  diameter: 61.14072
  label: Joyous
- position:
    x: 121
    y: 179
  diameter: 44.758068
  label: Melancholy
See more up to date version here.

Wednesday 18 September 2013

Reading and writing to a csv file ruby-processing

Whilst I was on a roll I thought I would rubify the load_table processing example, there really is no need to use processings convenience method load_table, we can do it all in pure ruby:-
#
# Loading Tabular Data
# after Daniel Shiffman, by Martin Prout.  
# 
# This example demonstrates how to use CSV
# to retrieve data from a CSV file and make objects 
# from that data.
#
# Here is what the CSV looks like:
#
#   x,y,diameter,name
#   160,103,43.19838,Happy
#   372,137,52.42526,Sad
#   273,235,61.14072,Joyous
#   121,179,44.758068,Melancholy
#
require 'csv'

load_library 'bubble'

attr_reader :bubbles, :data

def setup
  size(640, 360)
  load_data
end

def draw
  background(255)
  # Display all bubbles
  bubbles.each do |b|
    b.display
    b.rollover(mouse_x, mouse_y)
  end

  text_align(LEFT)
  fill(0)
  text('Click to add bubbles.', 10, height - 10)
end

def load_data
  # Load CSV file into an Array of Hash objects
  # :headers option indicates the file has a header row
  @data = CSV.read('data/data.csv', :headers => true).map{|row| row.to_hash}

  # The size of the array of Bubble objects is determined by the total number of 'rows' in the CSV
  @bubbles = []

  data.each do |row|
    # You access the values via their column name (set by using headers option above)
    x = row['x'].to_f
    y = row['y'].to_f
    d = row['diameter'].to_f
    n = row['name']
    # Make a Bubble object out of the data read
    bubbles << Bubble.new(x, y, d, n)
  end

end

def mouse_pressed
  # Create a new 'row' hash
  row = {'x' => mouse_x.to_s, 'y' => mouse_y.to_s, 'diameter' => rand(40..80).to_s, 'name' => 'Blah'}
  # add the row to the existing data array
  data << row
  # If the table has more than 10 rows 
  data.shift if (data.size > 10) # Delete the oldest row
  # read column names from data, and generate csv 'string' that can be written to file  
  column_names = data.first.keys
  s = CSV.generate do |csv|
    csv << column_names
    data.each do |row|
      csv << row.values
    end
  end
  # Writing the csv data back to the same file, (also specify UTF-8 format)
  File.open('data/data.csv', 'w:UTF-8') { |file| file.write(s)}
  # And reloading it
  load_data
end

NB: For a more upto date version see JRubyArt version

Monday 16 September 2013

Using json in ruby-processing

It is actually far easier to use rubys json library than processing/java, because all you need is a hash to convert to_json, and vice versa. Here is one of Daniel Shiffmans sketches given the ruby-processing treatment. It might be also possible to do something similar with XML using Nokogiri (ruby hash to XML) and rails supports (XML to ruby hash).
require "json"

attr_reader :bubbles, :bubble_data

def setup()
  size(640, 360)
  # read source_string from file
  load_data
end

def draw
  background 255
  bubbles.each do|bubble|
    bubble.display
    bubble.rollover(mouse_x, mouse_y)
  end
end

def load_data
  source_string = open("data/data.json", "r").read
  # parse the source string
  @bubble_data = BubbleData.new

  # get the bubble_data from the top level hash
  data = bubble_data.extract_data source_string
  @bubbles = []
  # iterate the bubble_data array, and create an array of bubbles
  data.each do |point|
    bubbles << Bubble.new(
      point["position"]["x"],
      point["position"]["y"],
      point["diameter"],
      point["label"])
  end
end

def save_data
  # demonstrate how easy it is to create json object from a hash in ruby
  # json = bubble_data.to_hash.to_json # if you don't require pretty output
  json = JSON.pretty_generate(bubble_data.to_hash) # pretty output
  # overwite existing 'data.json' 
  open("data/data.json", 'w') {|f| f.write(json) }
end

def mouse_pressed
  # create a new bubble instance, where mouse was clicked
  @bubble_data.add_bubble(Bubble.new(mouse_x, mouse_y, rand(40 .. 80), "new label"))
  save_data
  # reload the json data from the freshly created file
  load_data
end

class BubbleData
  attr_reader :name, :data
  def initialize name = "bubbles"
    @name = name
    @data = []
  end

  def add_bubble bubble
    data << bubble
  end

  def extract_data source_string
    @data = JSON.parse(source_string)[name]
  end

  def to_hash
    {name => data.map{|point| point.to_hash}}
  end

end

class Bubble
  attr_reader :x, :y, :diameter, :name, :over

  def initialize(x, y, diameter, name)
    @x, @y, @diameter, @name = x, y, diameter, name
    @over = false
  end

  def rollover px, py
    d = dist px, py, x, y
    @over = (d < diameter / 2.0)
  end

  def display
    stroke 0
    stroke_weight 2
    no_fill
    ellipse x, y, diameter, diameter
    if over
      fill 0
      text_align CENTER
      text(name, x, y + diameter / 2.0 + 20)
    end
  end

  def to_hash
    {"position" => {"x" => x, "y" => y}, "diameter" => diameter, "label" => name}
  end
end


The data "data.json"
{
  "bubbles": [
      {
        "position": {
          "x": 160,
          "y": 103
        },
        "diameter": 43.19838,
        "label": "Happy"
      },
      {
        "position": {
          "x": 372,
          "y": 137
        },
        "diameter": 52.42526,
        "label": "Sad"
      },
      {
        "position": {
          "x": 273,
          "y": 235
        },
        "diameter": 61.14072,
        "label": "Joyous"
      },
      {
        "position": {
          "x": 121,
          "y": 179
        },
        "diameter": 44.758068,
        "label": "Melancholy"
      }
    ]
}

Sunday 15 September 2013

Why ruby-processing?

No not _why ruby-processing, but what might make you want to explore ruby-processing?Well if you are coming from a ruby background, the answer is quite obvious you can write processing sketches in ruby (a language you know and love), and thanks to jruby mirroring regular ruby you are pretty much up to date (the same cannot be said for jython on which processing.py is based, which shows no signs of catching up with python any time soon).
Vanilla processing itself is in a bit of time-warp and only really supports language features consistent with java 5 (whereas java 7 is now the de-facto standard).
Perhaps it is the "permissive" nature of the ruby-language that is one of its most attractive features, which is well illustrated in the following sketch. Where you can readily extend the behaviour of a java class (PVector in this case), and what is more it is possible to do the same to an instance of a ruby array, ruby is a language that just lets you get things done (it does not stand in the way). Also it can be extremely elegant and expressive.
Then there is the community, rubyists are generally warm, welcoming and open, and thanks to the ecology (rubygems and the like eg json gem see example use here) you will find it is a lot easier to collaborate in ruby than many other programming environments (we also do not have a huge repo containing legacy code cf vanilla processing 1.38Gb+ they must be joking).
# Drawolver: draw 2D & revolve 3D

# Example to show how to extend Ruby classes in a useful way and how to
# use PVector and the Array is extended to yield one_of_each 
# pair of pts. See the drawolver library. Also features the use each_cons, 
# possibly a rare use for this ruby Enumerable method?
# 2010-03-22 - fjenett (last revised by monkstone 2013-09-13)

attr_reader :drawing_mode, :points, :rot_x, :rot_y, :vertices

module ExtendedArray
  # send one item from each array, expects array to be 2D:
  # array [[1,2,3], [a,b,c]] sends
  # [1,a] , [2,b] , [3,c]
  def one_of_each( &block )
    i = 0
    one = self[0]
    two = self[1]
    mi = one.length > two.length ? two.length : one.length
    while i < mi do
      yield( one[i], two[i] )
      i += 1
    end
  end
end


def setup
  size 1024, 768, P3D
  frame_rate 30
  reset_scene
end

def draw
  background 0
  if (!drawing_mode)
    translate(width/2, height/2)
    rotate_x rot_x
    rotate_y rot_y
    @rot_x += 0.01
    @rot_y += 0.02
    translate(-width/2, -height/2)
  end
  no_fill
  stroke 255
  points.each_cons(2) { |ps, pe| line ps.x, ps.y, pe.x, pe.y}

  if (!drawing_mode)
    stroke 125
    fill 120
    lights
    ambient_light 120, 120, 120
    vertices.each_cons(2) do |r1, r2|
      begin_shape(TRIANGLE_STRIP)
      ext_array = [r1,r2].extend ExtendedArray # extend an instance of Array
      ext_array.one_of_each do |v1, v2|
        vertex v1.x, v1.y, v1.z
        vertex v2.x, v2.y, v2.z
      end
      end_shape
    end
  end
end

def reset_scene
  @drawing_mode = true
  @points = []
  @rot_x = 0.0
  @rot_y = 0.0
end

def mouse_pressed
  reset_scene
  points << RPVector.new(mouse_x, mouse_y)
end

def mouse_dragged
  points << RPVector.new(mouse_x, mouse_y)
end

def mouse_released
  points << RPVector.new(mouse_x, mouse_y)
  recalculate_shape
end

def recalculate_shape
  @vertices = []
  points.each_cons(2) do |ps, pe|
    b = points.last - points.first
    len = b.mag
    b.normalize
    a = ps - points.first
    dot = a.dot b
    b = b * dot
    normal = points.first + b
    c = ps - normal
    nlen = c.mag
    vertices << []
    (0..TWO_PI).step(PI/15) do |ang|
      e = normal + c * cos(ang)
      e.z = c.mag * sin(ang)
      vertices.last << e
    end
  end
  @drawing_mode = false
end

# a wrapper around PVector that implements operators methods for +, -, *, /
#
class RPVector < Java::ProcessingCore::PVector

  def + (vect)
    RPVector.new self.x + vect.x, self.y + vect.y, self.z + vect.z
  end

  def - (vect)
    RPVector.new self.x - vect.x, self.y - vect.y, self.z - vect.z
  end

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

  def / (scalar)
    RPVector.new(self.x / scalar, self.y / scalar, self.z / scalar) unless scalar == 0
  end

end

Wednesday 11 September 2013

Gravitational Attraction (3D) Planetarium Sketch (avoiding reflection with some alternative logic)

Yet another example using Andrés Colubri's planetarium library, hardest to convert so far. Andrés Colubri is concerned that in his sketch some logic would be called too often in the draw loop, this is why he uses the pre and post (methods, only available using reflection, but only recognised if they are part of a java class, this is Catch 22 territory for ruby-processing). I think I've come up with a decent solution (certainly seems to run OK) that avoids reflection, which can only be good thing. In this case I have placed a "guard" in the pre and post methods (ruby methods to be clear). Can't get border to work "too complicated", I reckon I have mad sketch more efficient by replacing PVector with a custom vector (starts at 8 fps, but quickly moves to 60 fps).
The sketch:-
#
# Gravitational Attraction (3D) 
# by Daniel Shiffman.  
#
# Adapted for dome projection by Andres Colubri
# 
# Simulating gravitational attraction 
# G ---> universal gravitational constant
# m1 --> mass of object #1
# m2 --> mass of object #2
# d ---> distance between objects
# F = (G*m1*m2)/(d*d)
#
# For the basics of working with PVector, see
# http://processing.org/learning/pvector/
# as well as examples in Topics/Vectors/
# 
#
load_libraries :planetarium, :solar_system
include_package 'codeanticode.planetarium'

PLANETS = 10
attr_reader :angle, :planets, :sun, :count, :do_rotate
java_alias :background_int, :background, [Java::int] # precast for efficiency
def setup
  size(800, 800, Dome::RENDERER)
  @angle = 0                # Some random planets
  @planets = []
  @count = 10               # warm up time 
  @do_rotate = false
  (0 ... PLANETS).each do
    planets << Planet.new(rand(0.1 .. 2), rand(-width/2 .. width/2), rand(-height/2 .. height/2), rand(-100 .. 100))
  end
  # A single sun
  @sun = Sun.new
end

def pre
  if count == frame_count        # only enter once per frame
    planets.each do |planet|
      # Sun attracts Planets
      force = sun.attract(planet)
      planet.apply_force(force)
      # Update and draw Planets
      planet.update
    end
    @count = frame_count + 1
    frame.set_title("Solar System FPS: #{frame_rate.to_i}")
    @do_rotate = true
  end
end

def draw
  pre
  background_int 0
  # Setup the scene
  lights

  translate(width/2, height/2, 300)

  rotate_y(angle)

  # Display the Sun
  sun.display

  # All the Planets
  planets.each do |planet|
    planet.display
  end
  post
end

# Called after rendering all the faces, but before the dome sphere,
# so it can be used to draw stuff on the corners of the screen.
#def border
#  perspective
#  camera
#  background(255)
#  fill(0)
#  text("FPS: #{frame_rate}", 20, 20)
#end  

def post
  # Rotate around the scene
  if do_rotate
    @angle += 0.003
    @do_rotate = false
  end
end

The solar system library
 # Gravitational Attraction (3D) 
# Daniel Shiffman <http://www.shiffman.net>

# A class for an attractive body in our world

class Sun
  include Processing::Proxy
  G = 0.4       # Universal gravitational constant (arbitrary value)

  attr_reader :sphere, :location, :mass

  def initialize
    @location = Vect.new(0,0)
    @mass = 20
    @sphere = create_shape(SPHERE, mass * 2, 20)
    sphere.set_fill(false)
    sphere.set_stroke(color(255))
  end

  def constrain val, lo, hi                    # override processing overloaded method
    result = (val > hi)? hi : (val < lo)? lo : val
  end

  def attract(m)
    force = Vect.sub(location,m.location)      # Calculate direction of force
    d = constrain(force.mag, 5.0, 25.0)        # Limiting the distance to eliminate "extreme" results for very close or very far objects
    strength = (G * mass * m.mass) / (d * d)   # Calculate gravitional force magnitude
    force.set_mag(strength)                    # Get force vector --> magnitude * direction
    return force
  end

  # Draw Sun
  def display
    push_matrix
    translate(location.x,location.y,location.z)
    shape(sphere)
    pop_matrix
  end
end

# Gravitational Attraction (3D) 
# Daniel Shiffman <http://www.shiffman.net>

# A class for an orbiting Planet

class Planet 
  include Processing::Proxy
  # Basic physics model (location, velocity, acceleration, mass)
  attr_reader :location, :velocity, :acceleration, :mass, :sphere

  def initialize(m, x, y, z)
    @mass = m
    @location = Vect.new(x,y,z)
    @velocity = Vect.new(1,0)   # Arbitrary starting velocity
    @acceleration = Vect.new(0,0)
    @sphere = create_shape(SPHERE, mass * 8, 20)
    sphere.set_stroke(false)
    sphere.set_fill(color(255))
  end

  # Newton's 2nd Law (F = M*A) applied
  def apply_force(force)
    f = Vect.div(force,mass)
    acceleration.add(f)
  end

  # Our motion algorithm (aka Euler Integration)
  def update
    velocity.add(acceleration)  # Velocity changes according to acceleration
    location.add(velocity)      # Location changes according to velocity
    acceleration.zero
  end

  # Draw the Planet
def display
    push_matrix
    translate(location.x,location.y,location.z)
    shape(sphere)
    pop_matrix
  end
end

class Vect
  attr_reader :x, :y, :z
  def initialize(x, y, z = 0)
    @x, @y, @z = x, y, z
  end

  def normalize
    @x, @y, @z = x / mag, y / mag, z / mag  if (mag != 0 && mag != 1)
  end

  def mult a
    @x, @y, @z = x * a, y * a, z * a
  end

  def self.sub(v0, v1)
    Vect.new(v0.x - v1.x, v0.y - v1.y, v0.z - v1.z)
  end

  def add(v)
    @x, @y, @z = x + v.x, y + v.y, z + v.z
  end

  def self.div(v, a)
    Vect.new(v.x / a, v.y / a, v.z / a)
  end

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

  def set_mag m
    normalize
    @x, @y, @z = x * m, y * m, z * m
  end

  def zero
    @x, @y, @z = 0, 0, 0
  end
end

Monday 9 September 2013

Planetarium calibrate sketch in ruby-processing

load_libraries :planetarium, :control_panel
include_package 'codeanticode.planetarium'

attr_reader :cube_x, :cube_y, :panel, :hide, :camera, :grid

def setup
  # For the time being, only use square windows  
  size(600, 600, Dome::RENDERER)
  @camera = DomeCamera.new(self)
  @hide = false
  @grid = true
  control_panel do |c|
    c.title = "Control"
    c.look_feel "Metal"
    c.slider    :cube_x,  -width / 2.0 .. width / 2.0, -60.0
    c.slider    :cube_y,  -height / 2.0 .. height / 2.0, 60.0
    @panel = c
  end
end

def mouse_pressed
  @hide = false if hide  # use mouse click to restore control panel
end

# Called five times per frame.
def draw
  unless hide
    @hide = true
    panel.set_visible hide
  end
  face = camera.getFace
  case(face)
  when DomeCamera::POSITIVE_X
  background(240, 59, 31)
  when DomeCamera::NEGATIVE_X
  background(240, 146, 31)
  when DomeCamera::POSITIVE_Y
  background(30, 245, 0)
  when DomeCamera::NEGATIVE_Y
  background(30, 232, 156)
  when DomeCamera::POSITIVE_Z
  background(52, 148, 206)
  when DomeCamera::NEGATIVE_Z
  background(183, 115, 13)
  end

  push_matrix
  translate(width/2, height/2, 300)

  stroke(255)
  noFill

  push_matrix
  translate(cube_x, cube_y, 0)
  sphere_detail(8)
  sphere(30)
  pop_matrix

  lines_amount = 10
  (0 ... lines_amount).each do |i|
    ratio = i/(lines_amount - 1.0)
    line(0, 0, cos(ratio * TWO_PI) * 50, sin(ratio * TWO_PI) * 50)
  end
  pop_matrix
end

def key_pressed
  @grid = !grid
  (grid)? camera.set_mode(Dome::GRID) : camera.set_mode(Dome::NORMAL)
end

New Improved Planetarium Basic Sketch

That is more like it decent controls, no need for that reflection nonsense. Another example using Andrés Colubri's planetarium library.
# The planetarium library is designed to create real-time projections on 
# spherical domes. It is based on the FullDome project by Christopher 
# Warnow (ch.warnow@gmx.de):
# https://github.com/mphasize/FullDome
#
# A brief descrition on how it works: a 360° view of the scene is generated
# by rendering the scene 6 times from each direction: positive x, negative x, 
# positive y, and so on. The output of each rendering is stored inside a cube map 
# texture, which is then applied on a sphere representing the dome.
# Hence, the library calls the draw method 6 times per frame in order to update  
# the corresponding side of the cube map texture (in reality, only 5 times since  
# the bottom side of the cube map is not invisible on the dome). 
# New improved version using control panel no need for reflection "nastiness"

load_libraries :planetarium, :control_panel
include_package 'codeanticode.planetarium'

attr_reader :cube_x, :cube_y, :cube_z, :panel, :hide

def setup
  # For the time being, only use square windows  
  size(600, 600, Dome::RENDERER)
  @hide = false
  control_panel do |c|
    c.title = "Control"
    c.look_feel "Metal"
    c.slider    :cube_x,  -width / 2.0 .. width / 2.0, 10.0
    c.slider    :cube_y,  -height / 2.0 .. height / 2.0, 10.0
    c.slider    :cube_z,  -width / 2.0 .. 0, -20.0
    @panel = c
  end
end

def mouse_pressed
  @hide = false if hide  # use mouse click to restore control panel
end

# Called five times per frame.
def draw
  unless hide
    @hide = true
    panel.set_visible(hide)
  end
  background(0)

  push_matrix
  translate(width/2, height/2, 300)

  lights

  stroke(0)
  fill(150)
  push_matrix
  translate(cube_x, cube_y, cube_z)
  box(50)
  pop_matrix

  stroke(255)
  lines_amount = 10
  (0 ... lines_amount).each do |i|
    ratio = i/(lines_amount - 1.0)
    line(0, 0, cos(ratio * TWO_PI) * 50, sin(ratio * TWO_PI) * 50)
  end
  pop_matrix
end

Sunday 8 September 2013

Getting ready for jruby-1.7.5

Charlie has called for a big push on jruby-1.7.5 this weekend, I've tested current beta release with ruby-processing, and as far as I can I tell it will be good to go. I'm wondering whether there might be something else I could do to boost up the next release? Currently I'm thinking of dropping the perlin noise gem example which seems a bit iffy now. Perhaps another gem to replace it? I've been messing with Andres Colubri planetarium library (but will probably give it miss) can't get java reflection to work in reverse with "pre()".
But then I had brainwave!!!
# The planetarium library is designed to create real-time projections on 
# spherical domes. It is based on the FullDome project by Christopher 
# Warnow (ch.warnow@gmx.de):
# https://github.com/mphasize/FullDome
#
# A brief descrition on how it works: a 360° view of the scene is generated
# by rendering the scene 6 times from each direction: positive x, negative x, 
# positive y, and so on. The output of each rendering is stored inside a cube map 
# texture, which is then applied on a sphere representing the dome.
# Hence, the library calls the draw method 6 times per frame in order to update  
# the corresponding side of the cube map texture (in reality, only 5 times since  
# the bottom side of the cube map is not invisible on the dome). 
# I can't get 'pre' to work correctly on ruby processing (reflection in reverse)
# so here we use mouse_released instead

load_library :planetarium
include_package 'codeanticode.planetarium'

attr_reader :cube_x, :cube_y, :cube_z

def setup
  # For the time being, only use square windows  
  size(600, 600, Dome::RENDERER)
  @cube_x, @cube_y, @cube_z = width/2, height/2, -width/4
end

# Called one time per frame when pre, or to order via mouse.
def mouse_released
  # The dome projection is centered at (0, 0), so the mouse coordinates
  # need to be offset by (width/2, height/2)
  @cube_x += ((mouse_x - width * 0.5) - cube_x) * 0.2
  @cube_y += ((mouse_y - height * 0.5) - cube_y) * 0.2
end

# Called five times per frame.
def draw
  background(0)

  push_matrix
  translate(width/2, height/2, 300)

  lights

  stroke(0)
  fill(150)
  push_matrix
  translate(cube_x, cube_y, cube_z)
  box(50)
  pop_matrix

  stroke(255)
  lines_amount = 10
  (0 ... lines_amount).each do |i|
    ratio = i/(lines_amount - 1.0)
    line(0, 0, cos(ratio * TWO_PI) * 50, sin(ratio * TWO_PI) * 50)
  end
  pop_matrix
end

def key_pressed
  if (key == CODED)
    @cube_z -= 5 if (key_code == UP)
    @cube_z += 5 if (key_code == DOWN)
  end
end

Wednesday 4 September 2013

Dome Projection Shader

Latest processing shader example by Andres Colubri, sketch requires features that will be available in ruby-processing-2.1.5, also requires shader files, and to be run with the --nojruby flag:-
#
# DomeProjection
# 
# This sketch uses use environmental mapping to render the output 
# on a full spherical dome.
# 
# Based on the FullDome_template code from Christopher Warnow: 
# https://github.com/mphasize/FullDome
# 
#

attr_reader :fbo, :cube_map_shader, :dome_sphere, :env_map_texture_id

ENV_MAP_SIZE = 1024

def setup
  size(640, 640, P3D)
  init_cube_map
end

def draw
  background(0)
  draw_cube_map
end

def draw_scene
  background(0)
  stroke(255, 0, 0)
  stroke_weight(2)

  (-width ... 2 * width).step(50) do |i|
    line(i, -height, -100, i, 2 * height, -100)
  end
  (-height ... 2 * height).step(50) do |i|
    line(-width, i, -100, 2 * width, i, -100)
  end

  lights
  no_stroke
  translate(mouse_x, mouse_y, 200)
  rotate_x(frame_count * 0.01)
  rotate_y(frame_count * 0.01)
  box(100)
end

java_import "java.nio.IntBuffer"

def init_cube_map
  sphere_detail(50)
  @dome_sphere = create_shape(SPHERE, height/2.0)
  dome_sphere.rotate_x(HALF_PI)
  dome_sphere.set_stroke(false)

  pgl = beginPGL

  @env_map_texture_id = IntBuffer.allocate(1)
  pgl.gen_textures(1, env_map_texture_id)
  pgl.bind_texture(PGL::TEXTURE_CUBE_MAP, env_map_texture_id.get(0))
  pgl.texParameteri(PGL::TEXTURE_CUBE_MAP, PGL::TEXTURE_WRAP_S, PGL::CLAMP_TO_EDGE)
  pgl.texParameteri(PGL::TEXTURE_CUBE_MAP, PGL::TEXTURE_WRAP_T, PGL::CLAMP_TO_EDGE)
  pgl.texParameteri(PGL::TEXTURE_CUBE_MAP, PGL::TEXTURE_WRAP_R, PGL::CLAMP_TO_EDGE)
  pgl.texParameteri(PGL::TEXTURE_CUBE_MAP, PGL::TEXTURE_MIN_FILTER, PGL::NEAREST)
  pgl.texParameteri(PGL::TEXTURE_CUBE_MAP, PGL::TEXTURE_MAG_FILTER, PGL::NEAREST)
  (PGL::TEXTURE_CUBE_MAP_POSITIVE_X ... PGL::TEXTURE_CUBE_MAP_POSITIVE_X + 6).each do |i|
    pgl.texImage2D(i, 0, PGL::RGBA8, ENV_MAP_SIZE, ENV_MAP_SIZE, 0, PGL::RGBA, PGL::UNSIGNED_BYTE, nil)
  end

  # Init fbo, rbo
  @fbo = IntBuffer.allocate(1)
  rbo = IntBuffer.allocate(1)
  pgl.genFramebuffers(1, fbo)
  pgl.bindFramebuffer(PGL::FRAMEBUFFER, fbo.get(0))
  pgl.framebufferTexture2D(PGL::FRAMEBUFFER, PGL::COLOR_ATTACHMENT0, PGL::TEXTURE_CUBE_MAP_POSITIVE_X, env_map_texture_id.get(0), 0)

  pgl.genRenderbuffers(1, rbo)
  pgl.bindRenderbuffer(PGL::RENDERBUFFER, rbo.get(0))
  pgl.renderbufferStorage(PGL::RENDERBUFFER, PGL::DEPTH_COMPONENT24, ENV_MAP_SIZE, ENV_MAP_SIZE)

  # Attach depth buffer to FBO
  pgl.framebufferRenderbuffer(PGL::FRAMEBUFFER, PGL::DEPTH_ATTACHMENT, PGL::RENDERBUFFER, rbo.get(0))

  pgl.enable(PGL::TEXTURE_CUBE_MAP)
  pgl.active_texture(PGL::TEXTURE1)
  pgl.bind_texture(PGL::TEXTURE_CUBE_MAP, env_map_texture_id.get(0))

  endPGL

  # Load cubemap shader.
  @cube_map_shader = load_shader("cubemapfrag.glsl", "cubemapvert.glsl")
  cube_map_shader.set("cubemap", 1)
end

def draw_cube_map
  regenerateEnvMap
  drawDomeMaster
end

def drawDomeMaster
  ortho
  reset_matrix
  shader(cube_map_shader)
  shape(dome_sphere)
  reset_shader
end

# Called to regenerate the envmap
def regenerateEnvMap
  pgl = beginPGL

  # bind fbo
  pgl.bindFramebuffer(PGL::FRAMEBUFFER, fbo.get(0))

  # generate 6 views from origin(0, 0, 0)
  pgl.viewport(0, 0, ENV_MAP_SIZE, ENV_MAP_SIZE)
  perspective(90.0 * DEG_TO_RAD, 1.0, 1.0, 1025.0)
  (PGL::TEXTURE_CUBE_MAP_POSITIVE_X ... PGL::TEXTURE_CUBE_MAP_NEGATIVE_Z).each do |face|
    reset_matrix
    case face
    when PGL::TEXTURE_CUBE_MAP_POSITIVE_X
      camera(0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, -1.0, 0.0)
    when PGL::TEXTURE_CUBE_MAP_NEGATIVE_X
      camera(0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, -1.0, 0.0)
    when PGL::TEXTURE_CUBE_MAP_POSITIVE_Y
      camera(0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, -1.0)
    when PGL::TEXTURE_CUBE_MAP_NEGATIVE_Y
      camera(0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0)
    when PGL::TEXTURE_CUBE_MAP_POSITIVE_Z
      camera(0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -1.0, 0.0)
    end

    scale(-1, 1, -1)
    translate(-width * 0.5, -height * 0.5, -500)
    pgl.framebufferTexture2D(PGL::FRAMEBUFFER, PGL::COLOR_ATTACHMENT0, face, env_map_texture_id.get(0), 0)
    draw_scene# Draw objects in the scene
    flush# Make sure that the geometry in the scene is pushed to the GPU    
    no_lights # Disabling lights to avoid adding many times
    pgl.framebufferTexture2D(PGL::FRAMEBUFFER, PGL::COLOR_ATTACHMENT0, face, 0, 0)
  end

  endPGL
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