Ira Greenberg created this processing sketch, but it does not look very pretty in vanilla processing. Here I have used the ruby language to create a more elegant sketch, that is also somewhat more Object Orientated, without adding too many objects. In particular I have created Boundary Struct that makes it much easier to test boundary conditions for the VerletBall. I have further simplified the interfaces of both VerletBall and VerletStick which result in a much simplified sketch. I have even include an extra link it the chain but performance is fine.

verlet_ball.rb (in local library)

# frozen_string_literal: true
# Chain link end as a ball
class VerletBall
  include Processing::Proxy
  attr_reader :pos, :pos_old, :push, :radius, :x_bound, :y_bound

  def initialize(pos, push, radius)
    @pos = pos
    @push = push
    @radius = radius
    @pos_old = Vec2D.new(pos.x, pos.y)
    # start motion
    @pos += push
    @x_bound = Boundary.new(radius, width - radius)
    @y_bound = Boundary.new(radius, height - radius)
  end

  def run
    verlet
    render
    bounds_collision
  end

  def adjust(vec)
    @pos += vec
  end

  private

  def verlet
    pos_temp = pos.copy
    @pos += (pos - pos_old)
    @pos_old = pos_temp
  end

  def render
    ellipse(pos.x, pos.y, radius, radius)
  end

  def bounds_collision
    if x_bound.exclude? pos.x
      pos_old.x = constrain pos.x, x_bound.lower, x_bound.upper
      pos.x = (pos.x <= radius)? pos_old.x + push.x : pos_old.x - push.x
    end
    return unless y_bound.exclude? pos.y
    pos_old.y = constrain pos.y, y_bound.lower, y_bound.upper
    pos.y = (pos.y <= radius)? pos_old.y + push.y : pos_old.y - push.y
  end
end

# We can easily create and test bounds in ruby
Boundary = Struct.new(:lower, :upper) do
  def exclude? val
    true unless (lower..upper).cover? val
  end
end

verlet_stick.rb (in local library)

# frozen_string_literal: true
# Chain link end as a ball
class VerletStick
  include Processing::Proxy
  attr_reader :ball_1, :ball_2, :stiffness, :len

  def initialize(ball_1, ball_2, stiffness)
    @ball_1 = ball_1
    @ball_2 = ball_2
    @stiffness = stiffness
    @len = ball_1.pos.dist(ball_2.pos)
  end

  def run
    render
    constrain_len
  end

  private

  def render
    begin_shape
    vertex(ball_1.pos.x, ball_1.pos.y)
    vertex(ball_2.pos.x, ball_2.pos.y)
    end_shape
  end

  def constrain_len
    delta = ball_2.pos - ball_1.pos
    delta_length = delta.mag
    difference = ((delta_length - len) / delta_length)
    ball_1.adjust delta * (0.5 * stiffness * difference)
    ball_2.adjust delta * (-0.5 * stiffness * difference)
  end
end

The JRubyArt sketch


#
# Verlet Integration - ragdoll chain
# after a sketch by Ira Greenberg
#
load_library :verlet_chain
PARTICLES = 5
LINKS = PARTICLES - 1

attr_reader :sticks, :balls

def settings
  size(400, 400)
end

def setup
  sketch_title 'Verlet Chain'
  ellipse_mode(RADIUS)
  rshape = 40
  tension = 0.05
  @sticks = []
  @balls = []
  (0..PARTICLES).each do |i|
    push = Vec2D.new(rand(3..6.5), rand(3..6.5))
    pos = Vec2D.new(width / 2 + (rshape * i), height / 2)
    balls << VerletBall.new(pos, push, 5.0)
    next if i.zero?
    sticks << VerletStick.new(balls[i - 1], balls[i], tension)
  end
end

def draw
  background(255)
  sticks.each(&:run)
  balls.each(&:run)
end

To create the local library I created a verlet_chain folder (nested in a library folder) and I created simple verlet_chain.rb file that points to the classes.

verlet_chain.rb

frozen_string_literal: true
require_relative 'lib/verlet_ball'
require_relative 'lib/verlet_stick'

verlet_chain.ogv

PS: There is more ruby out there than you think!!!

This post is written in github flavored markdown, and served to you using a github flavored Jekyll (a ruby gem of course). Furthermore github is a Ruby on Rails project. The irony is that I’m writing this post using atom which is essentially a javascript project albeit helped along the way by coffeescript.

Refactoring the vanilla processing sketch

The very act of looking a the code afresh in ruby, led me to realise that was lot that could be done to refactor the vanilla processing code, see this gist.