Ruby Processing

Here is my blog in which I will describe my experiments with ruby-processing, find out more about ruby-processing at:- https://github.com/jashkenas/ruby-processing compatible with processing-2.2.1 and https://github.com/monkstone/cf3ruby for my version of the cfdg DSL (context-free-art)

Saturday, 18 April 2015

Sandi Metz suspicion of nil (pbox2d example)

Late last year Sandi Metz blogged about Suspicions of nil, more recently she presented Nothing is Something at Bath ruby, and I just watched the video at confreaks. The timing could not be much better I had just translated Dan Shiffmans MouseJoint sketch to run with ruby-processing, and the Spring class which has all these egregious checks for null (or nil in initial ruby version). I found that by using ruby duck-typing I could create a DummySpring class, that got rid of all those suspicious null checks!!!
The DummySpring class
# dummy_spring.rb by Martin Prout
# using duck-typing so class can stand in for Spring

# Using this class avoids test for nil
class DummySpring
  def initialize; end

  # If it exists we set its target to the mouse location
  def update(_x, _y); end

  def display; end

  # This is the key function where
  # we attach the spring to an x,y location
  # and the Box object's location
  def bind(x, y, box)
    spring = Spring.new
    spring.bind(x, y, box)
    spring
  end

  def destroy; end
end

The only slightly tricky thing was the sketch needs to know that DummyString bind returns an instance of Spring, but otherwise it all looks pretty cool. Would make a good example except that pbox2d confounds things (we have two worlds a jbox2d physics world and a processing sketch world, and we track both).

Monday, 6 April 2015

Using Forwardable in place of Processing::Proxy to mimic java inner class

In the following example we are using the pbox2d gem (in place of Dan Shiffmans Box2D-for-Processing to provide jbox2d. Here is the translated sketch:-
# The Nature of Code
# PBox2D example translated to use pbox2d gem by Martin Prout, and to use
# forwardable instead of Processing::Proxy to mimic java inner class access
# An uneven surface

require 'pbox2d'
load_library :surface

attr_reader :surface, :box2d, :particles

def setup
  size(500, 300)
  smooth 4
  # Initialize box2d physics and create the world
  @box2d = Box2D.new(self)
  box2d.init_options(gravity: [0, -20])
  box2d.create_world
  # to later set a custom gravity
  # box2d.gravity([0, -20])
  # Create the empty list
  @particles = []
  # Create the surface
  @surface = Surface.new(self)
end

def draw
  # If the mouse is pressed, we make new particles
  # We must always step through time!
  background(138, 66, 54)
  # Draw the surface
  surface.display
  # NB ? reqd to call mouse_pressed value, else method gets called.
  particles << Particle.new(self, mouse_x, mouse_y, rand(2.0..6)) if mouse_pressed?
  # Draw all particles
  particles.each(&:display)
  # Particles that leave the screen, we delete them
  # (note they have to be deleted from both the box2d world and our list
  particles.reject!(&:done)
  # Just drawing the framerate to see how many particles it can handle
  fill(0)
  text(format('framerate: %d', frame_rate.to_i), 12, 16)
end

Here is the associated library that uses Forwardable in place of Processing::Proxy. Place surface.rb in "library/surface" folder, or modify the sketch to use require_relative:-
require 'forwardable'

# The Nature of Code
# PBox2D example
# An uneven surface boundary
class Surface
  extend Forwardable
  # We'll keep track of all of the surface points
  def_delegators(:@app, :box2d, :width, :height, :begin_shape,
                 :end_shape, :fill, :stroke, :stroke_weight,
                 :vertex, :map1d, :noise)
  attr_reader :surface, :body, :y

  def initialize(app)
    @app = app
    @surface = []
    # This is what box2d uses to put the surface in its world
    chain = ChainShape.new
    # Perlin noise argument
    xoff = 0.0
    # This has to go backwards so that the objects  bounce off the top of the
    # surface. This "edgechain" will only work in one direction!
    (width + 10).step(-10, -5) do |x|
      # Doing some stuff with perlin noise to calculate a surface that points
      # down on one side and up on the other
      if x > width / 2
        @y = 100 + (width - x) * 1.1 + map1d(noise(xoff), (0..1.0), (-80..80))
      else
        @y = 100 + x * 1.1 + map1d(noise(xoff), (0..1.0), (-80..80))
      end
      # Store the vertex in screen coordinates
      surface << Vec2.new(x, y)
      # Move through perlin noise
      xoff += 0.1
    end
    # Build an array of vertices in Box2D coordinates
    # from the ArrayList we made
    vertices = []
    surface.each do |surf|
      vertices << box2d.processing_to_world(surf)
    end
    # Create the chain!
    chain.createChain(vertices, vertices.length)
    # The edge chain is now attached to a body via a fixture
    bd = BodyDef.new
    bd.position.set(0.0, 0.0)
    @body = box2d.createBody(bd)
    # Shortcut, we could define a fixture if we
    # want to specify frictions, restitution, etc.
    body.createFixture(chain, 1)
  end

  # A simple function to just draw the edge chain as a series of vertex points
  def display
    stroke_weight(2)
    stroke(0)
    fill(135, 206, 250)
    begin_shape
    vertex(width, 0)          # extra vertices so we can fill sky
    surface.map { |v| vertex(v.x, v.y) }     # the mountain range
    vertex(0, 0)              # extra vertices so we can fill sky
    end_shape
  end
end

# Using forwardable again
class Particle
  extend Forwardable
  def_delegators(:@app, :box2d, :push_matrix, :pop_matrix, :rotate,
                 :translate, :fill, :stroke, :stroke_weight, :noise,
                 :map1d, :ellipse, :line)
  # We need to keep track of a Body

  attr_reader :body, :x, :y, :r

  # Constructor
  def initialize(app, x, y, r)
    @app, @x, @y, @r = app, x, y, r
    # This function puts the particle in the Box2d world
    make_body(x, y, r)
  end

  # This function removes the particle from the box2d world
  def kill_body
    box2d.destroy_body(body)
  end

  # Is the particle ready for deletion?
  def done
    pos = box2d.body_coord(body)
    # Is it off the bottom of the screen?
    return false unless pos.y > box2d.height + r * 2
    kill_body
    true
  end

  def display
    # We look at each body and get its screen position
    pos = box2d.body_coord(body)
    # Get its angle of rotation
    a = body.get_angle
    push_matrix
    translate(pos.x,  pos.y)
    rotate(-a)
    fill(175)
    stroke(0)
    stroke_weight(1)
    ellipse(0, 0, r * 2, r * 2)
    # Let's add a line so we can see the rotation
    line(0, 0, r, 0)
    pop_matrix
  end

  # This function adds the particle to the box2d world
  def make_body(x, y, r)
    # Define and create the body
    bd = BodyDef.new
    bd.position = box2d.processing_to_world(x, y)
    bd.type = BodyType::DYNAMIC
    @body = box2d.create_body(bd)
    # Make the body's shape a circle
    cs = CircleShape.new
    cs.m_radius = box2d.scale_to_world(r)
    fd = FixtureDef.new
    fd.shape = cs
    # Parameters that affect physics
    fd.density = 1
    fd.friction = 0.01
    fd.restitution = 0.3
    # Attach fixture to body
    body.create_fixture(fd)
    # Give it a random initial velocity (and angular velocity)
    body.set_linear_velocity(Vec2.new(rand(-10..10), rand(5..10)))
    body.set_angular_velocity(rand(-10..10))
  end
end

Sunday, 5 April 2015

Replacing Processing::Proxy in ruby-processing

I have often thought that Processing::Proxy in ruby-processing is a bit of hack (further it doesn't always do what you need to mimic vanilla processings "inner" class access to methods/variables), here is a more respectable alternative using forwardable. As a bonus shows the neat Vec3D :to_vertex method in action:-
require 'forwardable'
load_libraries :icosahedron, :vecmath

def setup
  size 640, 360, P3D
  @ico1 = Icosahedron.new self, 75.0
  @ico2 = Icosahedron.new self, 75.0
  @ico3 = Icosahedron.new self, 75.0
end

def draw
  background 0
  lights
  translate(width / 2, height / 2)
  push_matrix
  translate(-width / 3.5, 0)
  rotate_x frame_count * PI / 185
  rotate_y frame_count * PI / -200
  stroke 170, 0, 0
  no_fill
  @ico1.draw
  pop_matrix
  push_matrix
  rotate_x frame_count * PI / 200
  rotate_y frame_count * PI / 300
  stroke 170, 0, 180
  fill 170, 170, 0
  @ico2.draw
  pop_matrix
  push_matrix
  translate(width / 3.5, 0)
  rotate_x frame_count * PI / -200
  rotate_y frame_count * PI / 200
  no_stroke
  fill 0, 0, 185
  @ico3.draw
  pop_matrix
end
Here is the library class that uses forwardable:-
# Example of replacing Processing::Proxy with extend Forwardable
# And using Vec3D :to_vertex function
class Icosahedron
  extend Forwardable   # replacing Processing::Proxy mixin
  def_delegators(:@app, :begin_shape, :end_shape)
  attr_reader :r, :app, :renderer

  def initialize(app, radius)
    @r = radius
    @app = app
    @renderer = AppRender.new(app)
  end

  ##
  # Draw an icosahedron defined by a radius r.
  #
  def draw
    # Calculate vertex data for an icosahedron inscribed by a sphere radius 'r'.
    # Use 4 Golden Ratio rectangles as the basis.
    phi = (1.0 + Math.sqrt(5.0)) / 2.0
    h = r / Math.sqrt(1.0 + phi * phi)
    v =
    [
      Vec3D.new(0, -h, h * phi), Vec3D.new(0, -h, -h * phi),
      Vec3D.new(0, h, -h * phi), Vec3D.new(0, h, h * phi),
      Vec3D.new(h, -h * phi, 0), Vec3D.new(h, h * phi, 0),
      Vec3D.new(-h, h * phi, 0), Vec3D.new(-h, -h * phi, 0),
      Vec3D.new(-h * phi, 0, h), Vec3D.new(-h * phi, 0, -h),
      Vec3D.new(h * phi, 0, -h), Vec3D.new(h * phi, 0, h)
    ]

    begin_shape(Java::ProcessingCore::PConstants::TRIANGLES)
    draw_triangle(v[0], v[7], v[4])
    draw_triangle(v[0], v[4], v[11])
    draw_triangle(v[0], v[11], v[3])
    draw_triangle(v[0], v[3], v[8])
    draw_triangle(v[0], v[8], v[7])
    draw_triangle(v[1], v[4], v[7])
    draw_triangle(v[1], v[10], v[4])
    draw_triangle(v[10], v[11], v[4])
    draw_triangle(v[11], v[5], v[10])
    draw_triangle(v[5], v[3], v[11])
    draw_triangle(v[3], v[6], v[5])
    draw_triangle(v[6], v[8], v[3])
    draw_triangle(v[8], v[9], v[6])
    draw_triangle(v[9], v[7], v[8])
    draw_triangle(v[7], v[1], v[9])
    draw_triangle(v[2], v[1], v[9])
    draw_triangle(v[2], v[10], v[1])
    draw_triangle(v[2], v[5], v[10])
    draw_triangle(v[2], v[6], v[5])
    draw_triangle(v[2], v[9], v[6])
    end_shape
  end

  def draw_triangle(p1, p2, p3)
    p1.to_vertex(renderer)
    p2.to_vertex(renderer)
    p3.to_vertex(renderer)
  end
end

Thursday, 2 April 2015

Using delegators in JRubyArt (where vanilla processing uses inner classes)

Using delegators in JRubyArt (where vanilla processing uses inner classes) and ruby-processing uses 'include Processing::Proxy' to mimic the same behaviour. See node.rb, where we selectively expose PApplets fill, stroke, stroke_weight and ellipse methods in the Node class.


force_directed_graph.rb
require 'jruby_art'
require 'toxiclibs'
require_relative 'cluster'
require_relative 'node'

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

  def setup
    size(640, 360)
    @f = create_font('Georgia', 12, true)
    @show_physics = true
    @show_particles = true
    # Initialize the physics
    @physics = Physics::VerletPhysics2D.new
    physics.set_world_bounds(Toxi::Rect.new(10, 10, width - 20, height - 20))
    # Spawn a new random graph
    new_graph
  end

  # Spawn a new random graph
  def new_graph
    # Clear physics
    physics.clear
    center = TVec2D.new(width / 2, height / 2)
    @clusters = (0..8).map { Cluster.new(physics, rand(3..8), rand(20..100), center) }
    # All clusters connect to all clusters
    clusters.each_with_index do |ci, i|
      clusters[i + 1..clusters.size - 1].each do |cj|
        ci.connect(cj)
      end
    end
  end

  def draw
    # Update the physics world
    physics.update
    background(255)
    # Display all points
    clusters.each(&:display) if show_particles
    # If we want to see the physics
    if show_physics
      clusters.each_with_index do |ci, i|
        ci.internal_connections self
        # Cluster connections to other clusters
        clusters[1 + i..clusters.size - 1].each do |cj|
          ci.show_connections(self, cj)
        end
      end
    end
    # Instructions
    fill(0)
    text_font(f)
    text("'p' to display or hide particles\n'c' to display or hide connections\n'n' for new graph", 10, 20)
  end

  # Key press commands
  def key_pressed
    case key
    when 'c'
      @show_physics = !show_physics
      @show_particles = true unless show_physics
    when 'p'
      @show_particles = !show_particles
      @show_physics = true unless show_particles
    when 'n'
      new_graph
    end
  end
end

ForceDirectedGraph.new(title: 'Force directed graphs')

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

  # We initialize a with a number of nodes, a diameter, and centerpoint
  def initialize(physics, n, d, center)
    @physics = physics
    @diameter = d
    @nodes = (0..n).map { Node.new(center.add(TVec2D.random_vector)) }
    # Connect all the nodes with a Spring
    nodes[1..nodes.size - 1].each_with_index do |pi, i|
      nodes[0..i].each do |pj|
        physics.add_spring(Physics::VerletSpring2D.new(pi, pj, diameter, 0.01))
      end
    end
  end

  def display
    nodes.each(&:display)
  end
  # This functons connects one cluster to another
  # Each point of one cluster connects to each point of the other cluster
  # The connection is a "VerletMinDistanceSpring"
  # A VerletMinDistanceSpring is a spring which only enforces its rest length if the
  # current distance is less than its rest length. This is handy if you just want to
  # ensure objects are at least a certain distance from each other, but don't
  # care if it's bigger than the enforced minimum.

  def connect(other)
    other_nodes = other.nodes
    nodes.each do |pi|
      other_nodes.each do |pj|
        physics.add_spring(Physics::VerletMinDistanceSpring2D.new(pi, pj, (diameter + other.diameter) * 0.5, 0.05))
      end
    end
  end

  # Draw all the internal connections
  def internal_connections(app)
    app.stroke(200, 0, 0, 80)
    nodes[0..nodes.size - 1].each_with_index do |pi, i|
      nodes[i + 1..nodes.size - 1].each do |pj|
        app.line(pi.x, pi.y, pj.x, pj.y)
      end
    end
  end

  # Draw all the connections between this and another Cluster
  def show_connections(app, other)
    app.stroke(200, 200, 0, 20)
    app.stroke_weight(2)
    other_nodes = other.nodes
    nodes.each do |pi|
      other_nodes[0..other_nodes.size - 1].each do |pj|
        app.line(pi.x, pi.y, pj.x, pj.y)
      end
    end
  end
end

node.rb
require 'forwardable'

# The Nature of Code
# Daniel Shiffman
# http://natureofcode.com
# Force directed graph
# Heavily based on: http://code.google.com/p/fidgen/
# Notice how we are using inheritance here!
# We could have just stored a reference to a VerletParticle object
# inside the Node class, but inheritance is a nice alternative
class Node < Physics::VerletParticle2D
  extend Forwardable
  def_delegators(:@app, :fill, :stroke, :stroke_weight, :ellipse)
  def initialize(pos)
    super(pos)
    @app = $app
  end

  # All we're doing really is adding a display function to a VerletParticle
  def display
    fill(50, 200, 200, 150)
    stroke(50, 200, 200)
    stroke_weight(2)
    ellipse(x, y, 16, 16)
  end
end

Wednesday, 25 March 2015

Implicit isosurface sketch re-worked for JRubyArt

Since the release of the toxiclibs gem (example below use gem version 0.3.0.pre), it is now quite easy to work with the toxiclibs libraries in JRubyArt see sketch below. This being a bare sketch should be run with 'k9 run implicit.rb'

#
# This example implements a custom VolumetricSpace uMath.sing 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)
#
# Usage:
# drag mouse to rotate camera
# mouse wheel zoom in/out
# l: apply laplacian mesh smooth
# 
#

# 
# Copyright (c) 2010 Karsten Schmidt & ruby-procesMath.sing version Martin Prout 2013
# This sketch relies on a custom ruby-procesMath.sing mesh_to_vbo library
# 
# 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
#

require 'toxiclibs'

load_library :mesh_to_vbo

RES = 64
ISO = 0.2
MAX_ISO = 0.66

attr_reader :mesh, :vbo, :curr_zoom, :implicit

def setup
  size(720,720, P3D)
  Processing::ArcBall.init(self)
  @vbo = MeshToVBO.new(self)
  @curr_zoom = 1
  vol = EvaluatingVolume.new(TVec3D.new(400,400,400), RES, RES, RES, MAX_ISO)
  surface = Volume::HashIsoSurface.new(vol)
  @mesh = Toxi::WETriangleMesh.new
  surface.compute_surface_mesh(mesh, ISO)
  @is_wire_frame = false
  no_stroke
  @implicit = vbo.meshToVBO(mesh, true)
  implicit.setFill(color(222, 222, 222))
  implicit.setAmbient(color(50, 50, 50))
  implicit.setShininess(color(10, 10, 10))
  implicit.setSpecular(color(50, 50, 50))
end

def draw
  background(0)
  lights
  define_lights
  shape(implicit)
end

def key_pressed
  case key
  when 'l', 'L'
    Toxi::LaplacianSmooth.new.filter(mesh, 1)
    @implicit = vbo.meshToVBO(mesh, true)
    # new mesh so need to set finish
    implicit.setFill(color(222, 222, 222))
    implicit.setAmbient(color(50, 50, 50))
    implicit.setShininess(color(10, 10, 10))
    implicit.setSpecular(color(50, 50, 50))
  when 's', 'S'
    save_frame("implicit.png")
  end
end

def define_lights
  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

class EvaluatingVolume < Volume::VolumetricSpace

  attr_reader :upper_bound, :lut
  FREQ = Math::PI * 3.8

  def initialize(scal_vec, resX, resY, resZ, upper_limit)
    super(scal_vec, resX, resY, resZ)
    @upper_bound = upper_limit
  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 = Math.sin(xx * FREQ) + Math.cos(yy * FREQ) + Math.sin(zz * FREQ)
      val = Math.cos(xx * FREQ) * Math.sin(yy* FREQ) + Math.cos(yy* FREQ) * Math.sin(zz* FREQ) + Math.cos(zz* FREQ)* Math.sin(xx* FREQ)
      if (val > upper_bound)
        val = 0
      end
    end
    return val
  end
end


The library code:-
############################################
# mesh_to_vbo.rb
# a ruby library to convert toxi.mesh object
# to vbo (PShape) written by Martin Prout
############################################
class MeshToVBO
  PShape = Java::ProcessingCore::PShape
  attr_reader :parent

  def initialize(parent)
    @parent = parent
  end

  def meshToVBO(mesh, smth)
    retained = parent.create_shape
    retained.begin_shape(PShape::TRIANGLES)
    if smth
      mesh.compute_vertex_normals
      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.get_faces.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_shape
    retained
  end

  # variant
  # input array of meshes, output an array of shapes
  def meshToRetained(mesh, smth)
    mesh.map { |m| meshToVBO(m, smth) }
  end
end

Tuesday, 24 March 2015

Toxiclibs gem released

I've just released a toxiclibs gem that can be used with JRubyArt or ruby-processing (examples uses gem version 0.3.0.pre))
# A ruby processing sketch (needs re-factoring for jruby_art)
#
#
# 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)
#
# Usage:
# drag mouse to rotate camera
# w: toggle wireframe on/off
# mouse wheel to zoom in/out
# l: apply laplacian mesh smooth
#
#

#
# Copyright (c) 2010 Karsten Schmidt & ruby-processing version Martin Prout 2012
# This sketch relies on a custom toxiclibscore library for PovRAY export
#
# 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
#

require 'toxiclibs'
load_library 'vecmath' # uncomment this line for ruby-processing
RES = 64
ISO = 0.2
MAX_ISO = 0.66
attr_reader :mesh, :gfx, :curr_zoom, :is_wire_frame

def setup
  size(720, 720, P3D)
  ArcBall.init(self)
  @gfx = Gfx::ToxiclibsSupport.new(self)
  vol = EvaluatingVolume.new(Toxi::Vec3D.new(400, 400, 400), RES, RES, RES, MAX_ISO)
  surface = Volume::HashIsoSurface.new(vol)
  @mesh = Toxi::WETriangleMesh.new
  surface.compute_surface_mesh(mesh, ISO)
  @is_wire_frame = false
end

def draw
  background(0)
  if is_wire_frame
    no_fill
    stroke(255)
  else
    fill(255)
    no_stroke
    define_lights
    lights
  end
  @gfx.mesh(mesh, true)
end

def key_pressed
  case key
  when 'w', 'W'
    @is_wire_frame = !is_wire_frame
  when 'l', 'L'
    Toxi::LaplacianSmooth.new.filter(mesh, 1)
  when 's', 'S'
    save_frame('implicit.png')
  end
end

def define_lights
  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

# Creating a volumetric space class
#
class EvaluatingVolume < Volume::VolumetricSpace
  include Processing::Proxy
  attr_reader :upper_bound
  FREQ = PI * 3.8

  def initialize(scal_vec, resX, resY, resZ, upper_limit)
    super(scal_vec, resX, resY, resZ)
    @upper_bound = upper_limit
  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 = cos(xx * FREQ) * sin(yy * FREQ) + cos(yy * FREQ) * sin(zz* FREQ) + cos(zz * FREQ) * sin(xx * FREQ)
      # val = sin(xx * FREQ) + cos(yy * FREQ) + sin(zz * FREQ)
      # val = sin(xx * FREQ) * (xx * FREQ) + sin(yy * FREQ) * (yy * FREQ) + sin(zz * FREQ) * (zz * FREQ)
      val = 0 if val > upper_bound
    end
    val
  end
end

Monday, 9 March 2015

Watch a Handy Sketch in ruby-processing

There is this other attempt at doing ruby-processing called processing.rb (by someone who appears to be allergic to objects) here is their signature sketch in ruby-processing, what is is wrong with 'rp5 watch'? Improvements made to their example (yes I know I cheated with boundary check):-
  • avoid unecessary use of instance eval (it is all over processing.rb examples)
  • use rand(range) this is ruby after all
  • use __persistent__ to warn ruby compiler of a singleton (advisable for jruby 9000)
  • use map to create an array
  • use Struct to create a simple Boundary class
load_library :handy
java_import org.gicentre.handy.HandyRenderer

BALL_NUM = 6
BALL_COLORS = [[255, 0, 0], [255, 255, 0], [64, 64, 255]]
attr_reader :balls, :handy

def setup
  size(400, 400)
  HandyRenderer.__persistent__ = true
  @handy = HandyRenderer.new(self)
  @balls = (0...BALL_NUM).map { |i| Ball.new(i, handy) }
end

def draw
  background(color(234, 215, 182))
  fill(color(0, 255, 0))
  handy.rect(20, 20, 360, 20)
  handy.rect(20, 360, 360, 20)
  handy.rect(20, 40, 20, 320)
  handy.rect(360, 40, 20, 320)
  balls.each(&:draw)
end

# Bouncing ball
class Ball
  include Processing::Proxy
  attr_reader :x, :y, :radius, :size, :renderer, :boundary
  def initialize(id, renderer)
    @renderer = renderer
    @x, @y = rand(100..300), rand(100..300)
    @vx, @vy = rand(-6..6), rand(-6..6)
    @size = rand(60..100)
    @radius = size / 2.0
    @color = color(*BALL_COLORS[id % BALL_COLORS.size])
    @boundary = Boundary.new(40 + radius, 360 - radius)
  end

  def draw
    @x += @vx
    @y += @vy
    @vy += 0.1
    @vx = -@vx unless boundary.include? x
    @vy *= -0.99 unless boundary.include? y
    fill(@color)
    renderer.ellipse(x, y, size, size)
  end
end

Boundary = Struct.new(:lower, :upper) do
  def include?(x)
    (lower...upper).cover? x
  end
end

Followers

About Me

My Photo
I am currently the lead developer of ruby-processing.