Drawing with Processing and Ruby

Dhaivat Pandya

processing2-logo

Processing is an environment/programming language that is meant to make visual, interactive applications extremely easy to write. It can be used for everything from teaching children how to code to visualizing scientific data.

Fortunately, all of this goodness is no longer bottled up inside Processing anymore; you can use it from Ruby! In this article, we’ll cover the ruby-processing gem and examples of Ruby/Processing in action.

Setting It Up

You’ll need copies of jRuby and Processing in order for ruby-processing to work correctly.

The instructions on how to get your setup working differ based on which Ruby environment manager you use (e.g. rbenv, rvm), your operating system, and Java version.

First of all, you’ll want to get a copy of jRuby. If you’re using RVM:

rvm install jruby
rvm use jruby

If you’re on rbenv,

rbenv install jruby-*version number*

Next up, you need a copy of Processing which is pretty easy since they have a nice download page.

We can get the ruby-processing gem:

gem install ruby-processing

Before we can get rolling, we need to tell ruby-processing where our copy of Processing is located. In the ~/.rp5rc YAML configuration file, set up the PROCESSING_ROOT variable. Fortunately, there’s a Processing file that sets up this variable for you. If you’re on Mac OS X, it should look something like this:

PROCESSING_ROOT: "/Applications/Processing.app/Contents/Java"

Enough configuration. Let’s get into the code.

Baby Steps

Here’s a peak at some Ruby/Processing code:

def setup
  size 200, 200
  background 0
  smooth
end

def draw
  fill 255, 102, 18
  ellipse 56, 46, 55, 55
end

The first thing you might notice is that this snippet has no immediately executable code! So, if we were to run it with the standard ruby interpreter, there would be no output. Instead, we have to run it with the special rp5 command:

rp5 run first.rb

Run it, wait a bit for the JVM to fire up, and you’ll see a colored circle staring at you. The code is incredibly simple. All Ruby/Processing programs have a setup method which is called only once while a “scene” is being set up. Then, we have a draw method which is called repeatedly and is supposed to update the screen. In our setup, we set the size of the screen, the background color (0 means black). Then, in draw, set the fill color using RGB values and draw a circle at coordinates (56, 46).

Processing has a fantastic reference, almost all of which is immediately transferrable (i.e. verbatim) to Ruby/Processing. It is important to note, however, that Processing employs camelCase for method names whereas the equivalent in Ruby uses underscores.

Now, let’s move onto something a little bit more complicated.

Particles

Let’s build a simple, interactive particle display. When you click, the particles should move toward the mouse position, then scatter when you let go of the mouse button.

class Particle
  attr_accessor :to_x, :to_y, :x, :y, :velocity

  def initialize(x, y, width, height)
    @x = x
    @y = y
    @velocity = 2

    @to_x = rand(width)
    @to_y = rand(height)
    @alpha = rand(255)
  end

  def draw
    stroke 255, 255, 255
    fill 150, 150, 150, 200
    ellipse @x, @y, 8, 8
  end

  def move
    mag = Math.sqrt(@x ** 2 + @y ** 2)
    @x = @x + @velocity * (@to_x - @x)/mag
    @y = @y + @velocity * (@to_y - @y)/mag
  end
end


def setup
  @bgcolor = "#2f2f2f"

  size displayWidth, displayHeight
  background color(@bgcolor)
  smooth

  @particles = []
  1000.times do
    @particles << Particle.new(rand(width), rand(height), width, height)
  end
end

def mouse_pressed
  @particles.each do |particle|
    particle.velocity = 6
    particle.to_x = mouse_x
    particle.to_y = mouse_y
  end
end

def mouse_released
  @particles.each do |particle|
    particle.to_x = rand(width)
    particle.to_y = rand(height)
    particle.velocity = 10
  end
end

def draw
  background color(@bgcolor)

  @particles.each do |particle|
    particle.draw
    particle.move
  end

end

Whoa, that looks like a ton of code! When broken down into bite-sized pieces, however, it turns out to be quite straightforward. Let’s take a look at the Particle class:

class Particle
  attr_accessor :to_x, :to_y, :x, :y, :velocity

  def initialize(x, y, width, height)
    @x = x
    @y = y
    @velocity = 2

    @to_x = rand(width)
    @to_y = rand(height)
    @alpha = rand(255)
  end

  def draw
    stroke 255, 255, 255
    fill 150, 150, 150, 200
    ellipse @x, @y, 8, 8
  end

  def move
    mag = Math.sqrt(@x ** 2 + @y ** 2)
    @x = @x + @velocity * (@to_x - @x)/mag
    @y = @y + @velocity * (@to_y - @y)/mag
  end
end

The initialize method is more or less boilerplate. The magic happens in draw, where we use the Ruby/Processing methods of stroke, fill, ellipse to draw the particle as a circle at a given point. Every particle has to_x and to_y attributes, which tell it where it is supposed to be heading. In the move method, we just use the distance formula to move the particle in the right direction.

def setup
  size displayWidth, displayHeight
  background color(@bgcolor)
  smooth

  @particles = []
  1000.times do
    @particles << Particle.new(rand(width), rand(height), width, height)
  end
end

Set the size to displayWidth and displayHeight, which allows us to create a semi-fullscreen experience on most platforms. Then, set the background color with background combined with color (I love these intuitive names). Finish up the setup by creating a list of a 1000 particles to draw.

def draw
  background color(@bgcolor)

  @particles.each do |particle|
    particle.draw
    particle.move
  end
end

We’re using a pretty common Processing idea: at the beginning of most frames, you’ll be clearing away everything with a call to background. Then, draw each particle on the screen, followed by a call to
move, shifting it closer to its to_x and to_y values. But, where are these values actually set?

def mouse_pressed
  @particles.each do |particle|
    particle.velocity = 6
    particle.to_x = mouse_x
    particle.to_y = mouse_y
  end
end

def mouse_released
  @particles.each do |particle|
    particle.to_x = rand(width)
    particle.to_y = rand(height)
    particle.velocity = 10
  end
end

Processing makes handling events really easy. Here, we are using mouse_pressed and mouse_released which are called when the mouse click button is pressed down and released, respectively. When the mouse is pressed, set the “to” values for every particle to point to the mouse location (causing them to all move toward the mouse). Then with mouse released, randomize the particle’s “to” values, causing them to spread away.

The effect this creates is actually pretty cool and pleasing to the eye. It is a bit simple, but it gives us the building blocks we can use to build more awesome stuff.

Graph Editor

When thinking of graphs, we usually think of two axes and some data plotted on their plane. Turns out that graphs are mathematical objects which consist of nodes and edges. They’re essentially points connected by lines. Generally, the angles between lines and the lengths of the lines are irrelevant. Instead, the concept of “connectivity” is more important. These graphs have all sorts of interesting properties. Let’s see if we can make a tool with Ruby/Processing to create graphs (strictly speaking, undirected graphs) like this one:

Image of a Graph

We’re going to need:

  • A way to create nodes
  • A way to create edges
  • A way to move around nodes while making sure the edges remain connected

Let’s get a Point class going (which represents a node):

class Point
  attr_accessor :x, :y, :segment_color, :to_points, :fill_color
  def initialize(x, y)
    @x = x
    @y = y
    @segment_color = color(100, 100, 100, 100)
    @radius = 30
    @fill_color = color(0)

    #points to draw segments to
    @to_points = []
  end

  def draw
    fill @fill_color
    ellipse @x, @y, @radius, @radius
  end

  def segment_to(other_point)
    stroke @segment_color
    line @x, @y, other_point.x, other_point.y
  end

  def in_thresh(x, y)
    thresh = @radius
    return (abs(@x - x) < thresh and abs(@y - y) < thresh)
  end
end

We’ve added two important methods compared to the Particle class we created earlier. segment_to creates the concept of lines between edges. The line Processing method draws lines, providing the coordinates of the starting and ending points as arguments. Finally, the in_thresh method allows us to determine whether or not a pair of coordinates is “close enough” to our point; it will be used to check if clicks coincide with a node.

Setting up the scene is quite simple:

def setup
  size displayWidth, displayHeight
  stroke_weight 3
  background(255)
  smooth

  @points = []
  7.times do
    @points << Point.new(rand(width), rand(height))
  end
end

First, setup the standard size and background, along with a call to stroke_weight which tells Processing how thick to make the lines. Also, initialize @points and add seven randomized nodes, just for kicks.

def draw
  background(255)

  @points.each do |point|
    point.draw
    point.to_points.each do |tp|
      point.segment_to tp
    end
  end
end

The draw method is just as simple. Simply draw the given points along with their associated edges. But, how do these edges get there in the first place?

def mouse_clicked
  clicked_points = @points.select do |p|
    p.in_thresh mouse_x, mouse_y
  end

  if clicked_points.length == 0
    @points << Point.new(mouse_x, mouse_y)
  elsif @from_node
    @from_node.to_points << clicked_points[0]
    @from_node.fill_color = color(0)
    @from_node = nil
  else
    @from_node = clicked_points[0]
    @from_node.fill_color = color(255, 0, 0)
  end
end

The way nodes are added should be simple: just click somewhere. In order to add an edge, click on one node, then click on another, and an edge is drawn in between.

Because of the way we are using clicks, there is some involved stuff under the mouse_clicked event. First, set up clicked_points as an array of points that are within range of the click. Then, if the click didn’t have anything to do with the existing points, create a new node. Subsequently, use the @from_node variable to determine whether this is the first or second click in creating an edge. If it is the first, “highlight” the node with a color to show the user that he/she is about to create an edge. If it is the second node, add the edge.

Notice, there isn’t any drawing code inside mouse_clicked. All of that stuff is handled inside the Point class and the draw method. Generally, your drawing code should remain separate from actual logic (a bit like the idea of separating side effects in functional languages).

What if we want to move the nodes around?

def mouse_pressed
  @points.each do |p|
    if p.in_thresh(mouse_x, mouse_y)
      @node = p
    end
  end
end

def mouse_dragged
  if @node
    @node.x = mouse_x
    @node.y = mouse_y
  end
end

Use the mouse_pressed and mouse_dragged events. The first is called as soon as the mouse button is pressed and the latter is called only when the mouse is dragged. In the former, set @node to the point that the user “presses” the mouse on. Then, in mouse_dragged, if the user has pressed on a point, move the point to wherever the mouse is. Note that mouse_dragged is called repeatedly as long as the user drags, so the node will move along with the mouse for the duration of the drag.

Let’s see the code in all its glory:

class Point
  attr_accessor :x, :y, :segment_color, :to_points, :fill_color
  def initialize(x, y)
    @x = x
    @y = y
    @segment_color = color(100, 100, 100, 100)
    @radius = 30
    @fill_color = color(0)
    #points to draw segments to
    @to_points = []
  end

  def draw
    fill @fill_color
    ellipse @x, @y, @radius, @radius
  end

  def segment_to(other_point)
    stroke @segment_color
    line @x, @y, other_point.x, other_point.y
  end

  def in_thresh(x, y)
    thresh = @radius
    return (abs(@x - x) < thresh and abs(@y - y) < thresh)
  end
end

def setup
  size displayWidth, displayHeight-160
  stroke_weight 3
  background(255)
  smooth

  @points = []
  7.times do
    @points << Point.new(rand(width), rand(height))
  end
end

def mouse_pressed
  @points.each do |p|
    if p.in_thresh(mouse_x, mouse_y)
      @node = p
    end
  end
end

def mouse_dragged
  if @node
    @node.x = mouse_x
    @node.y = mouse_y
  end
end

def mouse_clicked
  clicked_points = @points.select do |p|
    p.in_thresh mouse_x, mouse_y
  end

  if clicked_points.length == 0
    @points << Point.new(mouse_x, mouse_y)
  elsif @from_node
    @from_node.to_points << clicked_points[0]
    @from_node.fill_color = color(0)
    @from_node = nil
  else
    @from_node = clicked_points[0]
    @from_node.fill_color = color(255, 0, 0)
  end
end

def key_pressed
  if key == ' '
    @points = []
  end
end

def draw
  background(255)

  @points.each do |point|
    point.draw
    point.to_points.each do |tp|
      point.segment_to tp
    end
  end
end

After playing with it for a bit, it seems remarkably fluid and friendly. Although, we could add lots of features (e.g. saving to a file, a counter for number of edges, etc.), this is a nice foundation. Notice that we’ve used a collection of pretty simple calls like line, ellipse, background, etc. Processing provides us with this simplicity by using a state machine inside. Calls you make can affect the results of subsequent calls due to the fact that Processing holds state. This allows for very simple code, but debugging can become difficult because you have to mentally keep track of this state as long you work through your code.

Wrapping It Up

I hope you’ve enjoyed the tour of Processing through a Ruby lens. We’ve only covered a fraction of the Processing API, but we’ve shown its power in creating visuals with Ruby.

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

No Reader comments

Comments on this post are closed.