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

Thursday 7 May 2009

Animation of a ruby Saucer

Here is my revised ruby saucer animation, very similar to what I produced before using java processing, there are 3 classes, two using the Processing::Proxy mixin that provides the access to Processing api that use of an inner class does for the java implementation:-
The Saucer class ('saucer.rb')


# saucer.rb
class Saucer
  # makes use of mixin to replace java inner class
  include Processing::Proxy
  R1 = 4.0  # class constants that together
  R2 = 3.0  # with PI constants are used
  R3 = 6.0  # to define the Saucer shape
  A1 = 0.25
  A2 = 0.60
  A3 = 0.0
  MAX_THETA = QUARTER_PI/2
  # read accessors
  attr_reader :acc, :loc, :vel, :my_scale, :tilt, :wdth, :hght
  
  def initialize xpos, ypos, acc_y, acc_x, width, height
    @my_scale = 0.0
    @tilt = 0
    @loc = PVector.new xpos, ypos
    @vel = PVector.new 0, 0
    @acc = PVector.new acc_x, acc_y
    @focus = PVector.new xpos, ypos
    @wdth = width # frame width
    @hght = height # frame height
  end
  def height # copes with tilting saucer
    # avoided using ternary statement here
    wd = R3 * 2 * Math.sin(tilt) * my_scale
    ht = R2 * 2 * Math.sin(tilt) * my_scale
    if (ht > wd)
      return ht
    else
      return wd
    end
  end
  def width # copes with tilting saucer
    return R3 * 2 * Math.cos(tilt) * my_scale
  end
  def distance
    @focus.dist loc
  end
  
  def get_point radius, theta
    x = loc.x + (radius * Math.cos(theta + tilt)) *my_scale
    y = loc.y + (radius * Math.sin(theta + tilt)) *my_scale
    return x, y
  end
  def set_tilt(theta)
    theta = Saucer::MAX_THETA if theta > Saucer::MAX_THETA
    theta = -Saucer::MAX_THETA if theta < -Saucer::MAX_THETA
    @tilt = theta
  end
  def set_position x, y
    @loc.x = x
    @loc.y = y
  end
  def set_scale(scale)
    @my_scale = scale unless scale < 0
  end
  def render
    smooth
    curve_tightness(-0.05)
    begin_shape
      x, y = get_point(Saucer::R1, Saucer::A1)
      curve_vertex(x, y)
      x, y = get_point(Saucer::R2, TWO_PI - Saucer::A2)
      curve_vertex(x, y)
      x, y = get_point(Saucer::R2, PI + Saucer::A2)
      curve_vertex(x, y)
      x, y = get_point(Saucer::R1, PI - Saucer::A1)
      curve_vertex(x, y)
      x, y = get_point(Saucer::R2, PI + Saucer::A2)
      vertex(x, y)
      x, y = get_point(Saucer::R3, PI)
      vertex(x, y)
      vertex(x, y)
      x, y = get_point(Saucer::R1, PI - Saucer::A1)
      vertex(x, y)
      vertex(x, y)
      x, y = get_point(Saucer::R1, Saucer::A1)
      vertex(x, y)
      vertex(x, y)
      x, y = get_point(Saucer::R3, Saucer::A3)
      vertex(x, y)
      vertex(x, y)
      end_shape CLOSE
    end
    def update
      @vel.x += acc.x
      @vel.y += acc.y
      @loc.x += vel.x
      @loc.y += vel.y
      set_scale distance/30
      set_tilt vel.x * 0.04
    end
    def reset_x  
      @vel.x = 0
      # ternary statement gets the job done, best used for assignment?
      acc.x < 0 ? @loc.x = self.width/2 : @loc.x = (self.wdth - self.width/2)
      @acc.x *= -1
    end
    def reset_y  
      @vel.y = 0
      acc.y < 0 ? @loc.y = self.height/2 : @loc.y =(self.hght - self.height/2)
      @acc.y *= -1
    end
  end



The starfield animation Star class star.rb



# star.rb author Martin Prout
class Star
        # makes use of mixin to replace java inner class
        include Processing::Proxy
        attr_reader :acc, :loc, :star_size, :vel, :focus # NB accessors used by animation class
        def initialize(sz = 0.0, acc = nil, x = 0.0, y = 0.0)
                @star_size = sz
                @vel = PVector.new
                @acc = acc
                @focus = PVector.new X_FOCUS, Y_FOCUS
                # ensure stars are spread  out from focus initially in correct direction
                @loc = PVector.new(X_FOCUS + acc.x * 8000, Y_FOCUS + acc.y * 5000)
        end
        
        def distance # from focus
                @focus.dist loc
        end
        
        def brightness # star brightness
                distance * 1.3
        end
        
        def update
                @vel.x += acc.x
                @vel.y += acc.y
                @loc.x += vel.x
                @loc.y += vel.y
        end
        # reset_x == reset_y included to have common
        # interface with Saucer class
        def reset_x
                @loc.x = focus.x
                @loc.y = focus.y
                @vel.x = acc.x
                @vel.y = acc.y
        end
        def reset_y
                reset_x
        end
        
        def width
                star_size * 2
        end
        
        def height
                star_size * 2
        end    
        
        def render
                fill 255, 255, 255, brightness
                rect loc.x, loc.y, star_size, star_size
        end
end


Heres the code that drives the saucer animation


=begin
starfield animation, makes use of Processing::Proxy mixin
to have separate Star and Saucer classes with access to Processing
ide revised version as of 3rd July 2009
author Martin Prout
=end
require 'star'
require 'saucer'
# global constants
PHI = (1 + Math.sqrt(5))/2
STARS = 150
X_FOCUS = 800/PHI
Y_FOCUS = 150

class StarfieldSketch < Processing::App
  
  attr_reader :loc, :stars, :saucer

  def setup
    size 800, 500        
    smooth
    no_stroke
    frame_rate 60
    start_x, start_y = get_acceleration 0.005, 0.007
    if start_x.abs < 0.001 || start_y.abs < 0.0005 # guard against v. boring saucer
      start_x = 0.007 # only slightly boring saucer moves right and down initially
      start_y = 0.005
    end
    @saucer = Saucer.new X_FOCUS, Y_FOCUS, start_x, start_y, width, height
    @stars = []
    STARS.times {     # populate with stars, some bigger
      start_x, start_y = get_acceleration 0.002, 0.03
      @stars << Star.new(1.5, PVector.new(start_x, start_y)) unless stars.size % 3 == 0
      @stars << Star.new(2.2, PVector.new(start_x, start_y))  if stars.size % 3 == 0
    }  
  end

  def draw
    background 22, 22, 80
    stars.each { |star|      
      star.render
      star.update
      boundary star
    }
    fill 255, 0, 0  
    @saucer.render
    @saucer.update
    boundary @saucer
  end
  # returns a pair of random accelerations for use in PVector
  def get_acceleration min, max
          magnitude = rand * (max - min) + min
    theta = rand * TWO_PI # randomise direction using ruby rand function
    return magnitude*Math.cos(theta), magnitude*Math.sin(theta)
  end

  def boundary(star)
    star.reset_x unless star.loc.x < width - star.width/2
    star.reset_y unless star.loc.y < height - star.height/2
    star.reset_x unless star.loc.x > star.width/2
    star.reset_y unless star.loc.y > star.height/2
  end
end


To see the animation in action visit http://myweb.tiscali.co.uk/monkstone/animated_saucer/animated_ruby_saucer.html patience is required as whole jruby library is loaded ~9 Mb

No comments:

Post a Comment

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