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

Showing posts with label retained shape. Show all posts
Showing posts with label retained shape. Show all posts

Monday, 21 October 2013

Penrose Snowflake Revisited

The 1st time I revisited this sketch was in response to some refactoring by Dan Myer see http://learning-ruby-processing.blogspot.co.uk/2010/01/refactoring-my-penrose-snowflake.html. Since the advent of OPENGL for two dimensional sketches, that is all old hat, since we no longer need to keep generating the geometry, we can create a shape and reuse it (multiple times in the same sketch if need be). So here we use a generic grammar library for simple lsystems:-
############################
# Simple lsystem grammar
############################
class Grammar

  attr_reader :axiom, :rules
  def initialize(axiom, rules)
    @axiom = axiom
    @rules = rules
  end

  def expand(production, iterations, &block)
    production.each_char do |token|
      if rules.has_key?(token) && iterations > 0
        expand(rules[token], iterations - 1, &block)
      else
        yield token
      end
    end
  end

  def each gen
    expand(axiom, gen) {|token| yield token }
  end

  def generate gen
    output = []
    each(gen){ |token| output << token }
    return output
  end

end


Here is the animation code adapted from a processing example:-
# A class to describe a Polygon (with a PShape)

class Polygon
  include Processing::Proxy
  # The PShape object
  attr_reader :s, :x, :y, :speed, :height

  def initialize(s_, width, height)
    @x = rand(width)
    @y = rand(height * -1.5 .. -height / 3)
    @s = s_
    @speed = rand(2 .. 6)
    @height = height
  end

  # Simple motion
  def move
    @y +=speed
    @y = -100 if (y > height + 100)
  end

  # Draw the object
  def display
    push_matrix
    translate(x, y)
    shape(s)
    pop_matrix
  end

  def run
    display
    move
  end
end


Here is the penrose snowflake
load_libraries :grammar, :polygon

class PenroseSnowflake
  include Processing::Proxy
  import 'grammar'

  attr_accessor :axiom, :grammar, :start_length, :theta, :production, :draw_length,
  :repeats, :xpos, :ypos
  DELTA = Math::PI / 10 # 18 degrees as radians

  def initialize width
    reset width
  end

  def reset width
    @axiom = "F3-F3-F3-F3-F"
    @grammar = Grammar.new( axiom,
      {"F" => "F3-F3-F45-F++F3-F"}
      )
    @start_length = width / 40
    @theta = 0
    @xpos = width / 80
    @ypos = 0
    @draw_length = start_length
  end

  ##############################################################################
  # Parses the production string, and draws a line when 'F' is found, uses
  # trignometry to calculate dx and dy rather than processing transforms
  ##############################################################################

  def create_flake production
    flake = create_shape
    fil = [200, 220, 255].sample
    flake.begin_shape
    flake.fill fil
    flake.stroke fil
    flake.vertex(xpos, ypos)
    repeats = 1
    production.each do |element|
      case element
      when 'F'
        flake.vertex(@xpos -= multiplier(repeats, :cos), @ypos += multiplier(repeats, :sin))
        repeats = 1
      when '+'
        @theta += DELTA * repeats
        repeats = 1
      when '-'
        @theta -= DELTA * repeats
        repeats = 1
      when '3', '4', '5'
        repeats += element.to_i
      else
        puts "Character '#{element}' is not in grammar"
      end
    end
    flake.end_shape CLOSE
    return flake
  end

  ##########################################
  # adjust draw length with number of repeats
  # uses grammar to set production string 
  # see 'grammar.rb'
  ##########################################

  def create_grammar(gen)
    @draw_length *= 0.4**gen
    @production = grammar.generate gen
  end

  ###########################################
  # a helper method that returns dx or dy with type & repeat
  # multiplier after Dan Mayer
  ###########################################

  def multiplier(repeats, type)
    value = draw_length * repeats
    # using equal? for identity comparison
    (type.equal? :cos)?  value * cos(theta) : value *  sin(theta)
  end
end

##
# Lindenmayer System in ruby-processing by Martin Prout
# Very loosely based on a processing Penrose L-System
# by Geraldine Sarmiento
###

attr_reader :polygons

def setup
  size displayWidth, displayHeight, P2D
  #stroke 255
  flakes = []
  @polygons = []
  penrose = PenroseSnowflake.new width
  (2 .. 4).each do |i|
    production = penrose.create_grammar(i)
    flakes << penrose.create_flake(production)
    penrose.reset width
  end
  40.times do
    polygons << Polygon.new(flakes.sample, width, height)
  end
end

def draw
  background 0
  # Display and move them all
  polygons.each do |poly|
    poly.run
  end
end



Saturday, 27 April 2013

Hemesh mesh to VBO (PShape) in ruby-processing

Here is a sketch, pretty rich in ruby-processing syntax, including:-
  1. The building of a multi-dimensional array in ruby
  2. The casting of a multi-dimensional ruby array to a java array of float[][]
  3. Using "each" with a java iterator (it is so easy)
  4. Using bit shifting to create a color int
  5. Using the PShape vbo object
  6. A rubified do while block
The sketch:-
load_libraries :hemesh, :vbo

include_package 'wblut.math'
include_package 'wblut.processing'
include_package 'wblut.core'
include_package 'wblut.hemesh'
include_package 'wblut.geom'

RES = 20

attr_reader :mesh_ret, :inv_mesh_ret, :render

def setup
  size(800, 800, P3D)
  smooth(8)
  values = []               # build a multi-dimensional array in ruby
  (0 .. RES).each do |i|    # the inclusive range is intentional here
    valu = []
    (0 .. RES).each do |j|
      val = []
      (0 .. RES).each do |k|
        val << 2.1 * noise(0.35 * i, 0.35 * j, 0.35 * k)
      end
      valu << val
    end
    values << valu
  end

  creator = HEC_IsoSurface.new
  creator.set_resolution(RES,RES, RES) # number of cells in x,y,z direction
  creator.set_size(400.0/RES, 400.0/RES, 400.0/RES) # cell size

  # JRuby requires a bit of help to determine correct 'java args', particulary with 
  # overloaded arrays args as seen below. Note we are saying we have an 'array' of  
  # 'float array' here, where the values can also be double[][][].

  creator.set_values(values.to_java(Java::float[][])) # the grid points

  creator.set_isolevel(1)   # isolevel to mesh
  creator.set_invert(false) # invert mesh
  creator.set_boundary(100) # value of isoFunction outside grid
  # use creator.clear_boundary to set boundary values to "no value".
  # A boundary value of "no value" results in an open mesh

  mesh = HE_Mesh.new(creator)
  # mesh.modify(HEM_Smooth.new.set_iterations(10).setAutoRescale(true))
  creator.set_invert(true)

  inv_mesh = HE_Mesh.new(creator)
  inv_mesh.modify(HEM_Smooth.new.set_iterations(10).set_auto_rescale(true))
  @render = MeshToVBO.new(self)
  no_stroke
  # no color args produces a default light grey fill
  @mesh_ret = render.meshToVBO(mesh, color(200, 0, 0))
  @inv_mesh_ret = render.meshToVBO(inv_mesh, color(0, 0, 200))
end

def draw
  background(120)
  lights
  define_lights
  translate(400, 400)
  rotate_y(mouse_x.to_f / width * TWO_PI)  # use TWO_PI until processing-2.0b9
  rotate_x(mouse_y.to_f / height * TWO_PI) # then we can use TAU
  shape(inv_mesh_ret)
  shape(mesh_ret)
end

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

Here is the mesh to vbo library
# mesh_to_vbo.rb
module MS
  include_package 'java.util'
  include_package 'processing'
  include_package 'processing.core'
  include_package 'wblut.geom'
  include_package 'wblut.hemesh'
end

class MeshToVBO
  include Processing::Proxy
  include MS
  attr_reader :parent

  def initialize(parent)
    @parent = parent
  end

  def meshToVBO(mesh, col = nil)
    tri_mesh = mesh.get
    tri_mesh.triangulate
    retained = parent.create_shape
    retained.begin_shape(TRIANGLES)
    if col
      retained.fill(col)
    else # we will have a light grey color, created by bit shifting
      fcol = (255 >> 24) & 0xFF|(211 >> 16) & 0xFF|(211 >> 8) & 0xFF|211
      retained.fill(fcol)
    end
    retained.ambient(50)
    retained.specular(50)
    mesh.fItr.each do |face|  # call each on the hemesh mesh iterator
      he = face.getHalfedge
      begin      # this block is the ruby equivalent of do while
        vx = he.getVertex
        vn = vx.getVertexNormal
        retained.normal(vn.xf, vn.yf, vn.zf)
        retained.vertex(vx.xf, vx.yf, vx.zf)
        he = he.getNextInFace
      end while (he != face.getHalfedge)
    end
    retained.end_shape
    return retained
  end
end

Friday, 14 December 2012

MeshToVBO conversion ruby-processing

Since processing-2.0 there is the possibility of converting 3D meshes to vertical buffer objects (vbo) as instances of PShape. Here I explore a toxiclibs example sketch that instead of using toxis gfx package to render the shapes converts the shapes to vbo using a ruby library (albeit with a Processing::Proxy mixin. Here is the library:-
############################################
# mesh_to_vbo.rb
# a ruby library to convert toxi.mesh object
# to vbo (PShape) written by Martin Prout
# make use of Processing::Proxy mixin
############################################
class MeshToVBO
    include Processing::Proxy

    attr_reader :parent

    def initialize(parent)
        @parent = parent
    end

    def meshToVBO(mesh, smth)
        retained = parent.createShape(PShape::TRIANGLES)
        retained.enableStyle()
        retained.fill(222, 222, 222)
        retained.ambient(50)
        retained.shininess(10)
        retained.specular(50)
        if (smth)
            mesh.computeVertexNormals()
            mesh.getFaces.each do |f|
                retained.normal(f.a.normal.x, f.a.normal.y, f.a.normal.z)
                retained.vertex(f.a.x, f.a.y, f.a.z)
                retained.normal(f.b.normal.x, f.b.normal.y, f.b.normal.z)
                retained.vertex(f.b.x, f.b.y, f.b.z)
                retained.normal(f.c.normal.x, f.c.normal.y, f.c.normal.z)
                retained.vertex(f.c.x, f.c.y, f.c.z)
            end
        else
            mesh.getFaces.each do |f|
                retained.normal(f.normal.x, f.normal.y, f.normal.z)
                retained.vertex(f.a.x, f.a.y, f.a.z)
                retained.vertex(f.b.x, f.b.y, f.b.z)
                retained.vertex(f.c.x, f.c.y, f.c.z)
            end
        end
        retained.end()
        return retained
    end

    # variant
    # input array of meshes, output an array of shapes
    def meshToRetained(mesh, smth)
        rshapes = []
        (0 ... mesh.length).each do |i|
            rshapes.push(meshToVBO(mesh[i], smth))
        end
        return rshapes
    end
end



Here is the example sketch:-
# This example demonstrates how to rotate a number of meshes
# so that each points towards a common & user controlled focal point.
#
# Requires toxiclibs-0020 or newer
#
# (c) 2012 Karsten Schmidt / LGPL2 licensed
#
load_libraries 'toxiclibscore', 'vbo'

include_package 'toxi.geom'
include_package 'toxi.geom.mesh'

# container for mesh positions

attr_reader :positions, :vbo

def setup
  size(640,480,P3D)
  @vbo = MeshToVBO.new(self)
  @positions = []
  # compute mesh positions on circle in XZ plane
  (Circle.new(200).toPolygon2D(8)).each do |p|
    positions.push(p.to3DXZ)
  end
end

def draw
  background(51)
  lights
  no_stroke
  translate(width/2,height/2,0)
  rotate_x(-PI/6)
  # create manual focal point in XY plane
  focus = Vec3D.new((mouse_x - width/2), (mouse_y - height/2), 0)
  # create mesh prototype to draw at all generated positions
  # the mesh is a simple box placed at the world origin
  m = AABB.new(25).to_mesh
  # draw focus
  shape(vbo.meshToVBO(AABB.new(focus, 5).to_mesh, false))
  positions.each do |p|
    # align the positive z-axis of mesh to point at focus
    # mesh needs to be located at world origin for it to work correctly
    # only once rotated, move it to actual position
    shape(vbo.meshToVBO(m.copy.pointTowards(focus.sub(p), Vec3D::Z_AXIS).translate(p), false))
  end
  # draw connections from mesh centers to focal point
  stroke(0,255,255)
  positions.each do |p|
    line(p.x, p.y, p.z, focus.x, focus.y, focus.z)
  end
end

Wednesday, 5 December 2012

Retained Shape (FBO) in Ruby Processing

Now we are cooking, this example demonstrated running ruby-processing compiled with a pre-released version of processing-2.0b7. Further it demonstrates the use of the FBO to vastly improve performance. All sketches run with this ruby-processing hack are very noisy (complaining about ambigous overloaded java methods eg background and fill).

BOX_SIZE = 20
MARGIN = BOX_SIZE * 2
DEPTH = 400
FINT = 3

attr_reader :box_fill, :grid, :fcount, :lastm, :frate

def setup
  size(640, 360, P3D)
  frame_rate(60)
  @fcount = 0
  @lastm = 0
  no_smooth()
  no_stroke()
  @grid = create_shape(GROUP)
  # Build grid using multiple translations 
  (-(DEPTH/2 + MARGIN) ... (DEPTH/2 - MARGIN)).step(BOX_SIZE) do |i|
    (-(height + MARGIN) ... (height - MARGIN)).step(BOX_SIZE) do |j|
      (-(width + MARGIN) ... (width - MARGIN)).step(BOX_SIZE) do |k|
        # Base fill color on counter values, abs function 
        # ensures values stay within legal range
        @box_fill = color(i.abs.to_i, j.abs.to_i, k.abs.to_i, 50)
        cube = create_shape(BOX, BOX_SIZE.to_f, BOX_SIZE.to_f, BOX_SIZE.to_f)
        cube.fill(box_fill)
        cube.translate(k, j, i)
        grid.add_child(cube)
      end
    end
  end
end

def draw
  background(255)
  hint(DISABLE_DEPTH_TEST)
  # Center and spin grid
  push_matrix()
  translate(width/2, height/2, -DEPTH)
  rotate_y(frame_count * 0.01)
  rotate_x(frame_count * 0.01)
  shape(grid)
  pop_matrix()
  hint(ENABLE_DEPTH_TEST)
  @fcount += 1
  m = millis()
  if (m - lastm > 1000 * FINT)
    @frate = fcount / FINT
    @fcount = 0
    @lastm = m
    puts("fps: #{frate}")
  end
  fill(0)
  text("fps: #{frate}", 10, 20)
end

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