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

Showing posts with label Dan Shiffman. Show all posts
Showing posts with label Dan Shiffman. Show all posts

Thursday, 25 September 2014

Light-weight ruby-processing, heavy-weight example

Recently I had cause to mess with Philip Cunninghams Ribiprocessing (a light-weight ruby-processing, I've beefed it up a bit with some helper methods and Vec2D and Deglut jruby extensions). Here is a ruby-processing sketch I translated to work on my modified ribiprocessing (makes use of range clip function, a replacement for processing constrain):-
# Based on SeekingNeural example by Daniel Shiffman
# The Nature of Code
# http://natureofcode.com

require 'ribiprocessing'

module SeekingNeural

  class Perceptron
    # Perceptron is created with n weights and learning constant
    def initialize(n, c)
      @weights = Array.new(n) { rand(0..1.0) }
      @c = c
    end

    # Function to train the Perceptron
    # Weights are adjusted based on vehicle's error
    def train(forces, error)
      trained = @weights.zip(forces.map { |f| f.to_a }
        .map { |a, b| (a * error.x + b * error.y) * @c })
      .map { |w, c| (0.0 .. 1.0).clip(w + c)  }
      @weights = trained
    end

    # Give me a steering result
    def feedforward(forces)
      # Sum all values
      forces.zip(@weights).map { |a, b| a * b }.reduce(Vec2D.new, :+)
      # forces.zip(@weights).map { |a, b| a * b }.reduce(:+)
    end
  end

  # Seek
  # Daniel Shiffman <http://www.shiffman.net>

  class Vehicle
    MAX_SPEED = 4
    MAX_FORCE = 0.1

    attr_reader :brain, :sz, :location, :targets, :desired

    def initialize(n, x, y)
      @brain = Perceptron.new(n, 0.001)
      @acceleration = Vec2D.new
      @velocity = Vec2D.new
      @location = Vec2D.new(x, y)
      @sz = 6.0
    end

    # Method to update location
    def update(width, height)
      # Update velocity
      @velocity += @acceleration
      # Limit speed
      @velocity.set_mag(MAX_SPEED) { @velocity.mag > MAX_SPEED }
      @location += @velocity
      # Reset acceleration to 0 each cycle
      @acceleration *= 0
      @location.x = (0 .. width).clip location.x
      @location.y = (0 .. height).clip location.y
    end

    def apply_force(force)
      # We could add mass here if we want A = F / M
      @acceleration += force
    end

    # Here is where the brain processes everything
    def steer(targets, desired)
      # Steer towards all targets
      forces = targets.map { |target| seek(target) }
      # That array of forces is the input to the brain
      result = brain.feedforward(forces)
      # Use the result to steer the vehicle
      apply_force(result)
      # Train the brain according to the error
      error = desired - location
      brain.train(forces, error)
    end

    # A method that calculates a steering force towards a target
    # STEER = DESIRED MINUS VELOCITY
    def seek(target)
      desired = target - location  # A vector pointing from the location to the target
      # Normalize desired and scale to the maximum speed
      desired.normalize!
      desired *= MAX_SPEED
      # Steering = Desired minus velocity
      steer = desired - @velocity
      steer.set_mag(MAX_FORCE) { steer.mag > MAX_FORCE } # Limit to a maximum steering force
      steer
    end

    def display(app)
      # Draw a triangle rotated in the direction of velocity
      theta = @velocity.heading + Math::PI / 2
      app.fill(175)
      app.stroke(0)
      app.stroke_weight(1)
      app.push_matrix
      app.translate(location.x, location.y)
      app.rotate(theta)
      app.begin_shape
      app.vertex(0, -sz)
      app.vertex(-sz * 0.5, sz)
      app.vertex(sz * 0.5, sz)
      app.end_shape(Java::ProcessingCore.PConstants::CLOSE)
      app.pop_matrix
    end
  end
end


class Seeking < Ribiprocessing::SimpleApp
  include SeekingNeural

  # A Vehicle controlled by a Perceptron
  attr_reader :targets, :desired, :v


  def setup
    size(640, 360)
    # The Vehicle's desired location
    @desired = Vec2D.new(width / 2, height / 2)

    # Create a list of targets
    make_targets

    # Create the Vehicle (it has to know about the number of targets
    # in order to configure its brain)
    @v = Vehicle.new(targets.size, rand(width), rand(height))
  end

  # Make a random ArrayList of targets to steer towards
  def make_targets
    @targets = Array.new(8) { Vec2D.new(rand(width), rand(height)) }
  end

  def draw
    background(255)

    # Draw a circle to show the Vehicle's goal
    stroke(0)
    stroke_weight(2)
    fill(0, 100)
    ellipse(desired.x, desired.y, 36, 36)

    # Draw the targets
    targets.each do |target|
      no_fill
      stroke(0)
      stroke_weight(2)
      ellipse(target.x, target.y, 16, 16)
      line(target.x, target.y - 16, target.x, target.y + 16)
      line(target.x - 16, target.y, target.x + 16, target.y)
    end

    # Update the Vehicle
    v.steer(targets, desired)
    v.update(width, height)
    v.display self
  end

  def mouse_pressed
    make_targets
  end
end

Seeking.new title: 'Seeking Neural'


Update 27 September 2014, turns out ribiprocessing was a project that Philip Cunnigham created as university project but abandoned in favor of clojure!!! Didn't take too much to give it some pretty decent functionality. Update 4 October 2014, I have gone quite a further created a new ruby-processing implementation JRubyArt.

Wednesday, 17 September 2014

Using the Range class in ruby processing

Since ruby-processing-2.6.3 the processing constrain function is actually implemented using the clip function of Range (this method has been added to Range in ruby-processing). However in ruby processing we should probably prefer to use the clip method directly, as I have here in my World class:-
# Class provides an OO way constraining a Mover in a 2D space
# use
# world = World.new((0..width), (0..height))
# world.constrain_mover(mover)
class World
  attr_reader :xrange, :yrange
  def initialize(xrange, yrange)
    @xrange, @yrange = xrange, yrange
  end

  # @param mover is expected respond to loc, vel
  # that in turn respond to x and y getter/setters (Vec2D does this)

  def constrain_mover(mover)
    # Note clip functionality, extends Range in ruby-processing
    unless xrange.cover? mover.loc.x
      mover.vel.x *= -1
      mover.loc.x = xrange.clip mover.loc.x
    end
    return if yrange.cover? mover.loc.y
    mover.vel.y *= -1
    mover.loc.y = yrange.clip mover.loc.y
  end
end

For the sketch using this code see my fork Dan Shiffmans The Nature of Code Examples.

Sunday, 31 August 2014

Getting started with ruby-processing for wizards (best for vim users, though other editors can be used)

You have to start somewhere and for brevity I am assuming you've installed ruby-processing-2.6.2 here:-
rp5 create my_sketch 200 200
rp5 watch my_sketch.rb


In a new terminal start up vim
vim my_sketch.rb

def setup
  size 200, 200
end

def draw

end

Edit the sketch to change its background

def setup
  size 200, 200
end

def draw
  background 0
end

On saving the sketch refreshes for you

Now add a colored ellipse

def setup
  size 200, 200
end

def draw
  background 0
  fill 200, 0, 0
  ellipse 100, 100, 120, 100
end

Save the sketch again and is update once more, how cool is that!!!

Now this all easiest done using vim (I mean you only need two consoles and you are cooking, but you could do the same with emacs, jEdit, eric, textmate to name but a few decent editors). With processing-2.2.1 can also be use with PDF, P2D and P3D modes "rp5 create my_sketch 200 200 p3d" works to create P3D sketches. Now as far as I know there is no book on ruby processing but there is Dan Shiffmans nature of code, and the examples have been translated to ruby-processing here or for the beginner there is his Learning Processing examples translated here. Then there is the 300+ examples included with ruby-processing.........
The Nature of code book is now available in Japanese.

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