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

Monday 27 August 2012

A ArcBall library for ruby-processing

Here is my library, NB: three classes in a file named arcball.rb, and in a folder arcball nested in a library folder (NB: AVector is a low fat version of PVector for use here). Note you can put other ruby libraries in that folder. Note the use of :: separators and the Java prefix to call the EPSILON constant from vanilla processing.
class ArcBall
  attr_reader :center_x, :center_y, :v_down, :v_drag, :q_now, :q_drag, :q_down, :axis, :axis_set, :radius

  def initialize(cx, cy, radius)
    @center_x = cx
    @center_y = cy
    @radius = radius
    @v_down = AVector.new
    @v_drag = AVector.new
    @q_now = Quaternion.new
    @q_down = Quaternion.new
    @q_drag = Quaternion.new
    @axis_set = [AVector.new(1.0, 0.0, 0.0), AVector.new(0.0, 1.0, 0.0), AVector.new(0.0, 0.0, 1.0)]
    @axis = -1
  end

  def select_axis(axis)
    @axis = axis
  end

  def mouse2sphere(x, y)
    v = AVector.new((x - center_x) / radius, (y - center_y) / radius, 0)
    mag = v.x * v.x + v.y * v.y
    if (mag > 1.0)
      v.normalize
    else
      v.z = Math.sqrt(1.0 - mag)
    end
    v = constrain(v, axis_set[axis]) unless (axis == -1)
    return v
  end

  def mouse_pressed(x, y)
    @v_down = mouse2sphere(x, y)
    @q_down.copy(q_now)
    @q_drag.reset
  end

  def mouse_dragged(x, y)
    @v_drag = mouse2sphere(x, y)
    @q_drag.set(AVector.dot(v_down, v_drag), v_down.cross(v_drag))
  end


  def constrain(vector, axis)
    res = AVector.sub(vector, AVector.mult(axis, AVector.dot(axis, vector)))
    res.normalize
  end

  def update
    @q_now = Quaternion.mult(q_drag, q_down)
    quat2matrix(q_now)
  end

  def quat2matrix(q)
    q.get_value
  end
end

class Quaternion
  attr_reader :w, :x, :y, :z

  def initialize(w = 1.0,  x = 0,  y = 0,  z = 0)
    @w, @x, @y, @z = w,  x,  y,  z
  end

  def reset
    @w = 1.0
    @x = 0.0
    @y = 0.0
    @z = 0.0
  end

  def set(w, v)
    @w = w
    @x = v.x
    @y = v.y
    @z = v.z
  end

  def copy(q)
    @w = q.w
    @x = q.x
    @y = q.y
    @z = q.z
  end

  def self.mult(q1, q2)      # class method   
    x0 = q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y
    y0 = q1.w * q2.y + q1.y * q2.w + q1.z * q2.x - q1.x * q2.z
    z0 = q1.w * q2.z + q1.z * q2.w + q1.x * q2.y - q1.y * q2.x
    w0 = q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z
    Quaternion.new(w0,  x0,  y0,  z0)
  end

  def get_value
    sa = Math.sqrt(1.0 - w * w)
    sa = 1.0 unless (sa >= Java::processing::core::PConstants::EPSILON)
    [Math.acos(w) * 2, x / sa, y / sa, z / sa]
  end
end

class AVector

  attr_accessor :x, :y, :z

  def initialize(x = 0, y = 0, z = 0)
    @x, @y, @z = x, y, z
  end

  def add(vector)
    AVector.new(vector.x + x, vector.y + y, vector.z + z)
  end

  def normalize
    orig_dist = Math.sqrt(x * x + y * y + z * z)
    @x /= orig_dist
    @y /= orig_dist
    @z /= orig_dist
    self
  end

  def self.dot(v1, v2)
    v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
  end

  def self.mult(v, scalar)
    AVector.new(v.x * scalar, v.y * scalar, v.z * scalar)
  end

  def self.sub(v1, v2)
    AVector.new(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z)
  end

  def cross(v)
    AVector.new(y * v.z - v.y * z,  z * v.x - v.z * x, x * v.y - v.x * y)
  end

end

This is the test class test_arcball.rb:-
load_libraries 'arcball', 'opengl'
import "arcball"
import "opengl"

X = 0
Y = 1
Z = 2

attr_reader :my_ball

def setup
  size(600, 600, OPENGL)
  setup_opengl
  @my_ball = ArcBall.new(width/2.0, height/2.0, min(width - 20, height - 20) * 0.5)
end

def draw
  background(50, 50, 100)
  translate(width/2.0, height/2.0)  # @todo add zoom via z, using control_panel
  define_lights
  update
  lights
  stroke(0)
  cube(my_ball.radius)
end

def setup_opengl
  hint ENABLE_OPENGL_4X_SMOOTH     # optional
  hint DISABLE_OPENGL_ERROR_REPORT # optional
end

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

def mouse_pressed
  my_ball.mouse_pressed(mouse_x, mouse_y)
end

def mouse_dragged
  my_ball.mouse_dragged(mouse_x, mouse_y)
end

def define_lights
  ambient(20, 20, 20)
  ambient_light(50, 50, 50)
  point_light(30, 30, 30, 200, -150, 0)
  directional_light(0, 30, 50, 1, 0, 0)
  spot_light(30, 30, 30, 0, 40, 200, 0, -0.5, -0.5, PI / 2, 2)
end

def key_pressed                # @todo select via control_panel instead
  case(key)
  when 'x':
    my_ball.select_axis(X)
  when 'y':
    my_ball.select_axis(Y)
  when 'z':
    my_ball.select_axis(Z)
  end
end

def key_released
  my_ball.select_axis(-1)
end

def cube(sz)
  sz *= 0.5
  fill(200,  200,  200,  255)
  begin_shape(QUADS)
    vertex(-sz, -sz, -sz)
    vertex(+sz, -sz, -sz)
    vertex(+sz, +sz, -sz)
    vertex(-sz, +sz, -sz)
    vertex(-sz, -sz, +sz)
    vertex(+sz, -sz, +sz)
    vertex(+sz, +sz, +sz)
    vertex(-sz, +sz, +sz)
    vertex(-sz, -sz, -sz)
    vertex(-sz, -sz, +sz)
    vertex(-sz, +sz, +sz)
    vertex(-sz, +sz, -sz)
    vertex(+sz, -sz, -sz)
    vertex(+sz, -sz, +sz)
    vertex(+sz, +sz, +sz)
    vertex(+sz, +sz, -sz)
    vertex(-sz, -sz, -sz)
    vertex(+sz, -sz, -sz)
    vertex(+sz, -sz, +sz)
    vertex(-sz, -sz, +sz)
    vertex(-sz, +sz, -sz)
    vertex(+sz, +sz, -sz)
    vertex(+sz, +sz, +sz)
    vertex(-sz, +sz, +sz)
    end_shape
end



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