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)
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)
define_lights
update
lights
stroke(0)
cube(my_ball.radius)
end
def setup_opengl
hint ENABLE_OPENGL_4X_SMOOTH
hint DISABLE_OPENGL_ERROR_REPORT
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
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