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

Showing posts with label animation. Show all posts
Showing posts with label animation. Show all posts

Thursday, 14 October 2010

Creating simple animations using the SunflowAPIAPI library and jruby

Here I explore the animation possibilities of the SunflowAPIAPI library (nothing on the scale of the one by amnon.owed).
It is very simple to use ruby syntax to create different views of a scene by changing the camera position, and use sunflow to render each image. Rather than modify each frame, I had the idea of using 20 incremental views, and crudely converting them to 200 frames by copying each view 10 times. I then used mencoder to stitch the frames together to produce a short movie. Here is the result:-

Changing the camera Sunflow Hair from monkstone on Vimeo.




# hair.rb NB: run this script directly with jruby

require 'library/sunflow_api/library/sunflow_api.jar'
require 'library/sunflow/library/sunflow.jar'
require 'library/sunflow/library/janino.jar'
require 'library/sunflow/library/commons-compiler.jar'

class BasicHair 

  API = Java::Com::briansteen::SunflowAPIAPI
  SMath = Java::Org::sunflow::math
  JColor = java.awt.Color  
  
  attr_reader :width, :height, :n_particles, :sunflow
        
  def initialize width = 640, height = 480, cam_x = 0
    @width = width
    @height = height
    @n_particles = 20
    # create a new API instance
    @sunflow = API.new
    # set width and height
                
    @sunflow.set_width(width)
    @sunflow.set_height(height)
    # set background color
    @sunflow.set_background(1, 1, 1)
    # set camera
    @sunflow.set_camera_position(cam_x, 7, 5)
    @sunflow.set_camera_target(2, 0.5, 0)
    @sunflow.set_thinlens_camera("thinLensCamera", 50, width/height)
    # set basic light
    @sunflow.set_point_light("myPointLight", SMath::Point3.new(0, 5, 5),
    JColor.new(255, 255, 255))
    @sunflow.set_directional_light("myDirectionalLight", SMath::Point3.new(-2, 3, 0),
    SMath::Vector3.new(0, 0, 0), 3, JColor.new(1, 1, 1))
    # @sunflow.setSphereLight("mySphereLight", SMath::Point3.new(0, 30, -5),
    # JColor.new(0, 0, 255), 32, 10)
    # draw a ground plane
    @sunflow.draw_plane("ground", SMath::Point3.new(0, 0, 0), SMath::Vector3.new(0, 1, 0))              
    # coordinates array
                
    @sunflow.draw_box("boxname", 0, 0, 0, 1)
  end
  
  def create_scene
    hair_widths = [0.025]
    # create particle coordinates
    350.times do |j|
      # particle start position
      particle_x = Math.cos(j * 0.5) * j * 0.0015
      particle_y = 0
      particle_z = Math.sin(j * 0.5) * j * 0.0015 
      
      hair_coordinates = Array.new(n_particles * 3)
      
      array_index = -1
      
      n_particles.times do |i|
        particle_x += 0.1 + Math.cos(i * 0.15 + j * 0.05) * 0.13
        particle_y -= Math.sin(particle_z * 0.01 + j * 0.05) * 0.125 +
        Math.cos(i * 0.5 + particle_y) * 0.125
        particle_z += Math.sin(i) * 0.25 + particle_y * 0.01                            
        hair_coordinates[array_index += 1] = particle_x
        hair_coordinates[array_index += 1] = particle_y
        hair_coordinates[array_index += 1] = particle_z
      end
                        
      # set ambient occlusion shader
      @sunflow.setAmbientOcclusionShader("myAmbientOcclusionShader#{j}", JColor.new(55, 55, 55),
      JColor.new(0, 0, 0), 16, 1)
      # set glass shader
      # @sunflow.setGlassShader("myGlassShader", JColor.new(1, 1, 1), 2.5, 3, JColor.new(1, 1, 1))
      # set shiny-diffuse shader
      # @sunflow.setShinyDiffuseShader("myShinyShader", JColor.new(55, 55, 55), 0.8)
                        
      # draw object
      @sunflow.draw_hair("hair#{j}", n_particles - 2, hair_coordinates.to_java(:float),
        hair_widths.to_java(:float))
    end
  end
  
  def render_scene filename = nil
    sunflow.setIrradianceCacheGIEngine(32, 0.4, 1, 15, nil)            
    # render
    sunflow.render() unless filename
    if filename
      begin # test for dodgy filename/path
        file = File.new(filename, "w")
        file.close
        sunflow.render(filename) # save as png image
      rescue
        puts "Warning #{filename} is not writable"
      end
    end
  end
          
end

20.times do |i|
  hair = BasicHair.new 640, 480, i                   # preferred render size for vimeo
  hair.create_scene
  hair.render_scene "/home/tux/ruby_sunflow/frame-#{i}.png" # default is to render in sunflow frame ie not to file
end

Wednesday, 14 April 2010

An Translation Exercise (Very Clever Pseudo 3D Animation)

I've created a little ruby script for myself that translates (somewhat imperfectly) vanilla processing (ie. *.pde files) to ruby processing. Here is the tidied up version of a cool sketch I found on open-processing.

# Made by Jared "BlueThen" C.
# November 5, 2009.
# www.bluethen.com
# translated to ruby processing by monkstone April 14 2010
# variable a is used for determining the shape's y position, coupled with the distance they are from the center.
# setup(), the first function called when the applet is started

attr_reader :a, :y

def setup()
  @a = 0
  @y = 0
  # the applet is set to 500 pixels by 500 pixels
  size(500, 500)
  # RGB mode set to maximum of 6, since we'll be using 6 colors. 0 for black, 6 for white, and everything in between.
  color_mode(RGB, 6)
  # The stroke color is used to determine the border color of each quadrilateral.
  stroke(0)
  # frame rate is set to 30.
  frame_rate(30)
end

def draw()

  # a is decreased by 0.08. it represents the amount of radians the height of our boxes changes, and their speed.
  # if we did nothing to a, then none of our shapes will move, so a is a key component in our formulas.

  @a -= 0.08
  # screen is cleared and background is set to 6 (white).
  background(6)  

  # (x, z) is the ground, while y is vertical)  
  (-7..7).each do |x# loop over range for the x axis
    (-7..7).each do |z# loop over range for the z axis
    
      # The y variable is set to determine the height of the box.
      # We use formula radius * cos(angle) to determine this.
      # Since cosine, when graphed, creates a wave, we can use this to have the boxes transition from small to big smoothly.
      # The radius pretty much stands for our range. cosine alone will return values between -1 and 1, so we multiply this
      # by 24 to increase this value. the formula will return something in between -24 and 24.
      # The angle is in radians. an entire loop (circle) is 2PI radians, or roughly 6.283185.
      # distance is used to create the circular effect. it makes the boxes of the same radius around the center similar.
      # The distance ranges from 0 to 7, so 0.55 * distance will be between 0 and 3.85. this will make the highest and lowest
      # box a little more than half a loop's difference. a is added on, (subtracted if you want to be technical, since a is
      # negative), to provide some sort of change for each frame. if we don't include '+ a' in the algorithm, the boxes would
      # be still.
    
      @y = (24 * cos(0.55 * distance(x,z,0,0) + a)).round()
    
    
      # These are 2 coordinate variations for each quadrilateral.
      # since they can be found in 4 different quadrants (+ and - for x, and + and - for z),
      # we'll only need 2 coordinates for each quadrilateral (but we'll need to pair them up differently
      # for this to work fully).
      #
      # Multiplying the x and z variables by 17 will space them 17 pixels apart.
      # The 8.5 will determine half the width of the box ()
      # 8.5 is used because it is half of 17. since 8.5 is added one way, and 8.5 is subtracted the other way, the total
      # width of each box is 17. this will eliminate any sort of spacing in between each box.
      #
      # If you enable no_stroke(), then the whole thing will appear as one 3d shape. try it.
    
      xm = x * 17 - 8.5
      xt = x * 17 + 8.5
      zm = z * 17 - 8.5
      zt = z * 17 + 8.5
    
      # We use an integer to define the width and height of the window. this is used to save resources on further calculating
      halfw = width/2
      halfh = height/2
    
    
      # Here is where all the isometric calculating is done.
      # We take our 4 coordinates for each quadrilateral, and find their (x,y) coordinates using an isometric formula.
      # You'll probably find a similar formula used in some of my other isometric animations. however, I normally use
      # these in a function. to avoid using repetitive calculation (for each coordinate of each quadrilateral, which
      # would be 3 quads * 4 coords * 3 dimensions = 36 calculations).
      #
      # Formerly, the isometric formula was ((x - z) * cos(radians(30)) + width/2, (x + z) * sin(radians(30)) - y + height/2).
      # However, the cosine and sine are constant, so they could be precalculated. cosine of 30 degrees returns roughly 0.866,
      # which can round to 1, leaving it out would have little artifacts (unless placed side_by_side to accurate versions, where
      # everything would appear wider in this version) sine of 30 returns 0.5.
      #
      # We left out subtracting the y value, since this changes for each quadrilateral coordinate. (-40 for the base, and our y
      # variable) these are later subtracted in the actual quad().
    
      isox1 = (xm - zm + halfw).round
      isoy1 = ((xm + zm) * 0.5 + halfh).round
      isox2 = (xm - zt + halfw).round
      isoy2 = ((xm + zt) * 0.5 + halfh).round
      isox3 = (xt - zt + halfw).round
      isoy3 = ((xt + zt) * 0.5 + halfh).round
      isox4 = (xt - zm + halfw).round
      isoy4 = ((xt + zm) * 0.5 + halfh).round
    
      #the side quads. 2 and 4 is used for the coloring of each of these quads
      fill(2)
      quad(isox2, isoy2 - y, isox3, isoy3 - y, isox3, isoy3 + 40, isox2, isoy2 + 40)
      fill(4)
      quad(isox3, isoy3 - y, isox4, isoy4 - y, isox4, isoy4 + 40, isox3, isoy3 + 40)
    
    
      # the top quadrilateral.
      # y, which ranges between -24 and 24, multiplied by 0.05 ranges between -1.2 and 1.2
      # we add 4 to get the values up to between 2.8 and 5.2.
      # this is a very fair shade of grays, since it doesn't become one extreme or the other.
    
      fill(4 + y * 0.05)
      quad(isox1, isoy1 - y, isox2, isoy2 - y, isox3, isoy3 - y, isox4, isoy4 - y)
    end
  end
end

# the distance formula
def distance(x, y, cx, cy)
  sqrt(sq(cx - x) + sq(cy - y))
end







One of the problems with my script is that it turns all '-' to '_' also it can't cope with C++ style multi-line comments they should probably be banned anyway!!!

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

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