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

Tuesday 28 August 2012

Control Panel Enhanced ArcBall Sketch

Here is an enhanced arcball sketch, features use of control panel for zoom facility, and drop down menu too choose which or whether rotation is frozen about an axis. See previous post for library.
load_libraries 'arcball', 'opengl', 'control_panel'
import "arcball"
import "opengl"

X = 0
Y = 1
Z = 2

attr_reader :my_ball, :zoom

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)
  control_panel do |c|
    c.title = 'Controller'
    c.slider  :zoom, 0..2.0, 0.0
    c.menu(:freeze, ['X-axis', 'Y-axis', 'Z-axis', 'free'], 'free') {|m| load_menu_item(m) }
  end
  @zoom = 0
end

def draw
  background(50, 50, 100)
  translate(width/2.0, height/2.0, zoom * width/2.0)
  define_lights
  update
  lights
  stroke(0)
  cube(my_ball.radius / 2.0)
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 load_menu_item(m)
  case(m)
  when 'X-axis':
    my_ball.select_axis(X)
  when 'Y-axis':
    my_ball.select_axis(Y)
  when 'Z-axis':
    my_ball.select_axis(Z)
  when 'free'
    my_ball.select_axis(-1)
  end
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

sketch screenshot using the gimp

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



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