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

Showing posts with label zoom. Show all posts
Showing posts with label zoom. Show all posts

Sunday, 12 January 2014

Frame of Reference Example Sketch (Original by Ira Greenberg)

Here's another processing sketch that I've translated to ruby-processing, where I replace PVector with Vec3D from the ruby processing vecmath library. It also makes use of the ArcBall functionality built into the vecamth library.
###############
# Frame of Reference example by Ira Greenberg
# https://github.com/irajgreenberg/ProcessingTips
# Translated to ruby-processing by Martin Prout January 2014
# Now use mouse drag for ArcBall manipulation, and  +/- keys for zoom
###############

load_library :vecmath
load_library :geometry

FACE_COUNT = 50

attr_reader :arcball, :c, :p, :zoom

def setup
  size(800, 800, P3D)
  @zoom = 1.0
  camera(0, 0, (height/2.0) / tan(PI*30.0 / 180.0), 0, 0, 0, 0, -1, 0) # point camera at origin
  # create an Arcball at centre that pretty much fills the screen
  @arcball = ArcBall.new(0, 0, min(width - 20, height - 20) / 2.0)
  @c = []
  @p = []
  FACE_COUNT.times do |i|

    # calc some random triangles in 3 space
    val = Vec3D.new(rand(-width/2 .. width/2), rand(-width/2 .. width/2), rand(-width/2 .. width/2))
    v0 = Vec3D.new(rand(-val.x .. -val.x + 100), rand(-val.y .. -val.y + 100), rand(-val.z .. -val.z + 100))
    v1 = Vec3D.new(rand(-val.x .. -val.x + 100), rand(-val.y .. -val.y + 100), rand(-val.z .. -val.z + 100))
    v2 = Vec3D.new(rand(-val.x .. -val.x + 100), rand(-val.y .. -val.y + 100), rand(-val.z .. -val.z + 100))
    p << Plane.new([v0, v1, v2])

    # build some cute little cylinders
    c << Cylinder.new(Vec3D.new(150, 5, 5), 12)

    # Using each Triangle normal (N), 
    # One of the Triangle's edges as a tangent (T)  
    # Calculate a bi-normal (B) using the cross-product between each N and T
    # Note caps represent constants in ruby so we used N = nn, T = tt and B = bb in the ruby code below

    #
    # A picture helps
    # nice, sweet orthogonal axes 

    # N   B
    # |  /
    # | /
    # |/____T

    #
    # N, T, B together give you a Frame of Reference (cute little local coordinate system), based on each triangle. 
    # You can then take the cylinder (or any vertices) and transform them using a 3 x 3 matrix to this coordinate system.
    # (In the matrix each column is based on N, T and B respectivley.) 
    # The transform will handle any rotations and scaling, but not the translation, 
    # but we can add another dimenson to the matrix to hold the translation values.  
    # Here's what all this confusing description looks like:

    #
    # Matrix :                               Vector :
    # |  N.x  T.x  B.x  translation.x  |      |  x  |
    # |  N.y  T.y  B.y  translation.y  |      |  y  |
    # |  N.z  T.z  B.z  translation.z  |      |  z  |
    # |  0    0    0    1              |      |  1  |

    # We add the extra row in the matrix and the 1 to each vector 
    # so the math works. We describe the Matrix as 4 rows by 4 columns
    # and the vector now as a Matrix with 4 rows and 1 column. 
    # When you multiply matrices the inner numbers MUST match, so: 
    # [4 x 4] [4 x 1] is OK, but [4 x 4] [1 x 4] is NOT COOL.

    # (Please note there is also row vector approach that you can use,
    # Google about; it simply puts the vector on left side of matrix and treats
    # it as a 1 row and 4 column matrix. However, you'll also need to shift
    # the translation terms to the bottom of the matrix for the math to grock.)

    # The Matrix multiplication looks like this (sorry it's a little tedious looking.)
    # n.x * x + t.x *y + B.x * z + translation.x * 1  =  new transformed x
    # n.y * x + t.y *y + B.y * z + translation.y * 1  =  new transformed y
    # n.z * x + t.z *y + B.z * z + translation.z * 1  =  new transformed z
    # 0 * x + 0 *y + 0 * z + 1 * 1   =   disregard this crap.
    #

    nn = p[i].n
    tt = Vec3D.new(p[i].vecs[1].x - p[i].vecs[0].x, p[i].vecs[1].y - p[i].vecs[0].y, p[i].vecs[1].z - p[i].vecs[0].z)
    nn.normalize!
    tt.normalize!
    bb = nn.cross(tt)
    bb.normalize! # not really needed

    # build matrix with frame and translation (to centroid of each triangle)
    m4 = Mat4.new(nn, tt, bb, p[i].c)

    # transform each cylinder to align with each triangle
    c[i].vecs = m4.mult(c[i].vecs)
  end
  fill(187)
  stroke(50, 20)
end

def draw
  background(0)
  lights
  # update the arcball rotation
  update
  FACE_COUNT.times do |i|
    p[i].display
    c[i].display
  end
end

def update
  theta, x, y, z = arcball.update
  rotate(theta, x, y, z)
end

def mouse_pressed
  arcball.mouse_pressed(mouse_x, mouse_y)
end

def mouse_dragged
  arcball.mouse_dragged(mouse_x, mouse_y)
end

def key_pressed
  case key
  when '+'
    @zoom -= 0.1  # closer is bigger
  when '-'
    @zoom += 0.1
  end
  camera(0, 0, (height * zoom / 2.0) / tan(PI*30.0 / 180.0), 0, 0, 0, 0, -1, 0)
end


Here is the Plane class
NORM_LEN = 225.0

class Plane
  include Processing::Proxy

  attr_reader :vecs, :c, :n


  def initialize(vecs)
    @vecs = vecs
    init
  end

  def init
    v1 = vecs[1].dup
    v2 = vecs[2].dup
    v1 -= vecs[0]
    v2 -= vecs[0]

    @c = Vec3D.new(
      (vecs[0].x+vecs[1].x+vecs[2].x) / 3,
      (vecs[0].y+vecs[1].y+vecs[2].y) / 3,
      (vecs[0].z+vecs[1].z+vecs[2].z) / 3
      )

    @n = v1.cross(v2)
    n.normalize!
  end

  def display
    begin_shape(TRIANGLES)
    vecs.each do |vec|
      vertex(vec.x, vec.y, vec.z)
    end
    end_shape

    #normal
    stroke(200, 160, 30)
    begin_shape(LINES)
    vertex(c.x, c.y, c.z)
    vertex(c.x + n.x * NORM_LEN, c.y + n.y * NORM_LEN, c.z + n.z * NORM_LEN)
    end_shape

    #binormal
    stroke(160, 200, 30)
    begin_shape(LINES)
    vertex(c.x, c.y, c.z)
    # tangent
    v = vecs[1].dup
    #v.set(vecs[1])
    v -= vecs[0]
    v.normalize!
    vertex(c.x + v.x * NORM_LEN, c.y + v.y * NORM_LEN, c.z + v.z * NORM_LEN)
    end_shape

    stroke(30, 200, 160)
    begin_shape(LINES)
    vertex(c.x, c.y, c.z)
    b = v.cross(n)
    vertex(c.x + b.x * NORM_LEN, c.y + b.y * NORM_LEN, c.z + b.z * NORM_LEN)
    end_shape
    stroke(0, 75)
  end
end



Here is the Mat4 class
# uber simple Homogeneous 4 x 4 matrix

class Mat4
  attr_reader :mat

  def initialize(axisX, axisY, axisZ, trans)
    @mat = [
    [axisX.x, axisY.x, axisZ.x, trans.x],
    [axisX.y, axisY.y, axisZ.y, trans.y],
    [axisX.z, axisY.z, axisZ.z, trans.z],
    [0, 0, 0,  1]
    ]
  end

  # The processing version changes the input 'array', here we return
  # a new array with transformed values (which we then assign to the input)
  # see line 91 Frame_of_Reference.rb

  def mult(array)
    temp = []
    array.each do |arr|
      xt = mat[0][0] * arr.x + mat[0][1] * arr.y + mat[0][2] * arr.z + mat[0][3] * 1
      yt = mat[1][0] * arr.x + mat[1][1] * arr.y + mat[1][2] * arr.z + mat[1][3] * 1
      zt = mat[2][0] * arr.x + mat[2][1] * arr.y + mat[2][2] * arr.z + mat[2][3] * 1
      temp << Vec3D.new(xt, yt, zt)
    end
    return temp
  end
end


Here is the Cylinder class
class Cylinder
  include Processing::Proxy
  attr_accessor :vecs
  attr_reader :detail, :dim



  def initialize(dim, detail)
    @dim = dim
    @detail = detail
    init
  end

  def init
    theta = 0.0
    #    created around x-axis
    #    y = Math.cos
    #    z = Math.sin
    veca = []
    vecb = []
    detail.times do
      veca << Vec3D.new(0, Math.cos(theta)*dim.y, Math.sin(theta)*dim.z)
      vecb << Vec3D.new(dim.x, Math.cos(theta)*dim.y, Math.sin(theta)*dim.z)
      theta += Math::PI * 2/detail
    end
    @vecs = veca.concat(vecb)
  end

  def display
    begin_shape(QUADS)
    detail.times do |i|
      if (i<detail-1)
        vertex(vecs[i].x, vecs[i].y, vecs[i].z)
        vertex(vecs[i+1].x, vecs[i+1].y, vecs[i+1].z)
        vertex(vecs[detail+i+1].x, vecs[detail+i+1].y, vecs[detail+i+1].z)
        vertex(vecs[detail+i].x, vecs[detail+i].y, vecs[detail+i].z)
      else
        vertex(vecs[i].x, vecs[i].y, vecs[i].z)
        vertex(vecs[0].x, vecs[0].y, vecs[0].z)
        vertex(vecs[detail].x, vecs[detail].y, vecs[detail].z)
        vertex(vecs[detail+i].x, vecs[detail+i].y, vecs[detail+i].z)
      end
    end
    end_shape
  end
end

Sunday, 12 September 2010

A Mouse Wheel Listener in Ruby Processing

For my 3D context free application/library, I would like to be able to zoom using the mouse wheel, I had vaguely thought about using peasycam or proscene to provide that, and other functionality. In the first instance I wanted to create it myself. After a bit of research, this the sort of implementation I would do using the java classes/interfaces in ruby:-

# mouse_listener.rb

class JWheelListener
  include java.awt.event.MouseWheelListener

  attr_reader :zoom

  def initialize(zoom)
    @zoom = zoom
  end

  def mouse_wheel_moved(e)
    @zoom += e.get_wheel_rotation * 10
  end

end

class Mouse < Processing::App

  attr_reader :wheel

  def setup
    size(1000, 1000)
    @wheel = JWheelListener.new(10)
    self.add_mouse_wheel_listener(@wheel)  
  end

  def draw
    background 0
    fill 255, 0, 0
    ellipse(width/2, height/2, wheel.zoom, wheel.zoom)

  end

end

Mouse.new :title => "Mouse Wheel Listener"




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