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

Showing posts with label Sandi Metz. Show all posts
Showing posts with label Sandi Metz. Show all posts

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).

Thursday, 4 September 2014

New features in ruby-processing-2.6.2

# Hooky class demonstrates how to use the post_initialize hook,
# available since ruby-processing-2.6.2, to add additional attribute
# in this case :background as an array of int (splat to color)
# Not sure how clever it is to have multiple sketch instances.

class Hooky < Processing::App
  attr_reader :back

  def setup
    size 200, 200
    background(*back)
  end

  def post_initialize(args)
    @back = (args[:background])
  end
end

red = [200, 0, 0]
green = [0, 200, 0]
blue = [0, 0, 200]
colors = [red, green, blue]

colors.each_with_index do |col, i|
  Hooky.new(x: i * 90, y: i * 90, title: "Hooky #{i}", background: col)
end


Read more about post-initialization hooks in POODR by Sandi Metz.

Monday, 1 September 2014

Getting started with ruby-processing for ruby purists

Previously in my post getting started for wizards I showed how to create a sketch that would be more familiar to people coming from processing to ruby-processing. The sketch does not need to be explicitly wrapped as a class (that extends from Processing::App), since ruby-processing does that for you under the hood. This is the DSL approach to writing ruby-processing sketches which I favour, and makes it easier to read across from other processing modes to ruby-processing. However since ruby-processing-2.6.0 it is just as easy to create a classical sketch.
rp5 create classic_sketch 200 200 --wrap
rp5 watch classic_sketch.rb


Open a new console and 'vim classic_sketch.rb' or us another suitable editor

class ClassicSketch < Processing::App
  def setup
    size 200, 200
  end

  def draw

  end
end

# ClassicSketch.new(x: 20, y: 20)

For instant gratification and to relocate the sketch on your display un-comment the last line of the sketch and save (also note this line was "not" required to run the sketch, rp5 run or rp5 watch takes care of creating a new instance of sketch for you). You only use this form to send parameters, such as 'x offset' to your sketch

class ClassicSketch < Processing::App
  def setup
    size 200, 200
  end

  def draw

  end
end

ClassicSketch.new(x: 20, y: 20)
The sketch updates auto-magically...
Already in the works for the next release is the possibility of placing the sketch on your screen using the config file '~/.rp5rc' so this is kind of redundant, but a post initialization hook might get added so you will be able to send all sorts of other parameters. Which are then available by overriding the post_initialization hook method (See Sandi Metz POODR)

Next change the background of your sketch

class ClassicSketch < Processing::App
  def setup
    size 200, 200
  end

  def draw
    background 0
  end
end

ClassicSketch.new(x: 20, y: 20)

Next create a blue box.

class ClassicSketch < Processing::App
  def setup
    size 200, 200
  end

  def draw
    background 0
    fill 0, 0, 200
    rect 40, 50, 120, 100
  end
end

ClassicSketch.new(x: 20, y: 20)


To create class wrapped P3D sketch just "rp5 create classy_sketch 200 200 p3d --wrap" actually it probably doesn't matter where you put the --wrap option after create (but the order of the other variables is important)...

Tuesday, 24 June 2014

Decoupling Sub Classes Using Hook Messages (in ruby-processing)

About time I posted a bit more code:-
# An example demonstrating the use of a hook to decouple subclasses in 
# ruby-processing. The base class here is Spin, and we have included 
# post_initialize as the hook (it does nothing in the base class). SpinArm
# and SpinSpots both subclass Spin, but only SpinSpots requires the hook to
# change the initialization. Note the use the hook means the subclass does 
# not need to call super

def setup
  size 640, 360
  @arm = SpinArm.new({ x: width/2, y: height/2, s: 0.01 })
  @spots = SpinSpots.new({ x: width/2, y: height/2, s: -0.02, d: 90.0 })
end

def draw
  background 204
  @arm.display
  @spots.display
end


class Spin

  attr_accessor :x, :y, :speed
  attr_accessor :angle

  def initialize(args = {})
    @x, @y = args[:x], args[:y]
    @speed = args[:s]
    @angle = args[:angle] || 0.0
    post_initialize(args)  # this is the hook
  end

  def update
    @angle += speed
  end

  def post_initialize args
    nil
  end

end


class SpinArm < Spin # inherit from (or "extend") class Spin
  # NB: initialize inherited from Spin class

  def display
    stroke_weight 1
    stroke 0
    push_matrix
    translate x, y
    update
    rotate angle
    line 0, 0, 165, 0
    pop_matrix
  end
end


class SpinSpots < Spin
  attr_accessor :dim

  def post_initialize args
    @dim = args[:d]
  end

  def display
    no_stroke
    push_matrix
    translate x, y
    update
    rotate angle
    ellipse(-dim/2, 0, dim, dim)
    ellipse(dim/2, 0, dim, dim)
    pop_matrix
  end
end

Saturday, 5 October 2013

A custom Vector library for ruby processing

I recently got my copy of Practical Object Oriented Design in Ruby by Sandi Metz book, this got me thinking what could I could apply the ideas to in ruby-processing, and here is an early crack at it. Creating a custom (pure-ruby) vector library which can replace the hybrid RPVector (that extends PVector from processing) class of a previous post. This is very much a first crack (but it works, only difference needed is load_library :vec and use normalize! instead of normalize, much more ruby like I think) because I have ideas for extending the functionality along some of these lines, however my sentiment is with toxi re simple made easy. Further if I use vanilla processing logic the cross_product method (modulus, dist etc) could all live in Vec, but I can easily defer that decision. I haven't at this stage made use distance_squared externally, however this will be more efficient for testing boundary conditions of say a bouncing ball than regular dist. Not shown here are the rspec tests that I've been using to ensure the code behaves, I am tempted to bundle this and a Quaternion class and possibly some others as a core ruby-processing library, since the operations of PVector are not at all ruby like, and many more people seem to come to ruby-processing from ruby, not the other way round sadly.
class Vec
  attr_accessor :x, :y, :z
  EPSILON = 9.999999747378752e-05     # a value used by processing.org
  def initialize(x = 0 ,y = 0, z = 0)
    @x, @y, @z = x, y, z
    post_initialize
  end

  def post_initialize
    nil
  end

  def ==(vec)
    (x - vec.x).abs < EPSILON && (y - vec.y).abs < EPSILON && (z - vec.z).abs < EPSILON
  end
end


class Vec2D < Vec

  # Modulus of vec. Also known as length, size or norm
  def modulus
    Math.hypot(x, y)
  end

  def self.dist_squared(vec_a, vec_b)
    (vec_a.x - vec_b.x)**2 + (vec_a.y - vec_b.y)**2
  end

  def self.dist(vec_a, vec_b)
    Math.hypot(vec_a.x - vec_b.x, vec_a.y - vec_b.y)
  end

  # vanilla processing returns a Vector, rather than Scalar (defaults to 3D result when z = 0)
  def cross_product(vec)
    x * vec.y - y * vec.x
  end

  # Scalar product, also known as inner product or dot product
  def dot(vec)
    x * vec.x + y * vec.y
  end

  def collinear_with?(vec)
    cross_product(vec).abs < EPSILON
  end

  def +(vec)
    Vec2D.new(x + vec.x, y + vec.y)
  end

  def -(vec)
    Vec2D.new(x - vec.x, y - vec.y)
  end

  def *(scalar)
    Vec2D.new(x * scalar, y * scalar)
  end

  def / (scalar)
    Vec2D.new(x / scalar, y / scalar) unless scalar == 0
  end

  def normalize!
    @x, @y = x / modulus, y / modulus
    return self
  end

  alias :mag :modulus

end

class Vec3D < Vec

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

  def self.dist_squared(vec_a, vec_b)
    (vec_a.x - vec_b.x)**2 + (vec_a.y - vec_b.y)**2 + (vec_a.z - vec_b.z)**2
  end

  def self.dist(vec_a, vec_b)
    Math.sqrt(self.dist_squared(vec_a, vec_b))
  end


  def cross_product(vec)
    xc = y * vec.z - z * vec.y
    yc = z * vec.x - x * vec.z
    zc = x * vec.y - y * vec.x
    Vec3D.new(xc, yc, zc)
  end

  # Scalar product, also known as inner product or dot product
  def dot(vec)
    x * vec.x + y * vec.y + z * vec.z
  end

  def collinear_with?(vec)
    cross_product(vec) == Vec3D.new
  end

  def +(vec)
    Vec3D.new(x + vec.x, y + vec.y, z + vec.z)
  end

  def -(vec)
    Vec3D.new(x - vec.x, y - vec.y, z - vec.z)
  end

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

  def / (scalar)
    Vec3D.new(x / scalar, y / scalar, z / scalar) unless scalar.abs < EPSILON
  end

  def normalize!
    @x, @y, @z = x / modulus, y / modulus, z / modulus
    return self
  end

  alias :mag :modulus
end

A Little Test courtesy of Daniel Shiffman
#
# Vector 
# by Daniel Shiffman.  
# 
# Demonstration some basic vector math: subtraction, normalization, scaling
# Normalizing a vector sets its length to 1.
#
load_library :vec

def setup
  size(640,360)
end

def draw
  background(0)

  # A vector that points to the mouse location
  mouse = Vec2D.new(mouse_x, mouse_y)
  # A vector that points to the center of the window
  center = Vec2D.new(width/2,height/2)
  # Subtract center from mouse which results in a vector that points from center to mouse
  mouse = mouse - center # note need assign result to mouse

  # Normalize the vector the ! means we are changing the value of mouse
  mouse.normalize!

  # Multiply its length by 150 (Scaling its length)
  mouse = mouse * 150 # note need assign the result to mouse

  translate(width/2,height/2)
  # Draw the resulting vector
  stroke(255)
  stroke_weight(4)
  line(0, 0, mouse.x, mouse.y)
end

Now interestingly the following also works so you can use the
+=, -=, /=, and *= assignment methods ( but you cannot override += etc as a methods unlike C++ )
So what this means is that in ruby += etc are just syntactic shortcuts, and not an operator in its own right (which is probably a good thing).
#
# Vector 
# by Daniel Shiffman.  
# 
# Demonstration some basic vector math: subtraction, normalization, scaling
# Normalizing a vector sets its length to 1.
#
load_library :vec

def setup
  size(640,360)
end

def draw
  background(0)

  # A vector that points to the mouse location
  mouse = Vec2D.new(mouse_x, mouse_y)
  # A vector that points to the center of the window
  center = Vec2D.new(width/2,height/2)
  # Subtract center from mouse which results in a vector that points from center to mouse
  mouse -= center  # note we can assign result to mouse using -=

  # Normalize the vector the ! means we are changing the value of mouse
  mouse.normalize!

  # Multiply its length by 150 (Scaling its length)
  mouse *= 150 # note we can assign result to mouse using *=

  translate(width/2,height/2)
  # Draw the resulting vector
  stroke(255)
  stroke_weight(4)
  line(0, 0, mouse.x, mouse.y)

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