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

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

No comments:

Post a Comment

Followers

Blog Archive

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