Recently I have been exploring a couple of Scheme implementations of cfdg, one of those used opengl as a back end. That got me interested in the idea of exploring ruby-opengl bindings and this is the result (I found this rather old example of glxgears by Arto Bendiken, which was in need of updating anyway, the array entry for color and light attributes are now Struct rather than array). I got a bit carried away as I've replaced the callback methods with new stabby ruby lambda syntax, anyway result is rather impressive. Main thing bugging is I can't give the window a title (the escape key now works for me, since I replace 27 with ?\e).
#!/usr/bin/env ruby1.9
# coding: utf-8
# 3-D gear wheels. This program is in the public domain.
#
# Command line options:
# -info print GL implementation information
# -exit automatically exit after 30 seconds
#
# 2005-05-01 Ruby version by Arto Bendiken based on gears.c rev 1.8.
# 2005-01-09 Original C version (gears.c) by Brian Paul et al.
# http://cvs.freedesktop.org/mesa/Mesa/progs/demos/gears.c?rev=1.8
# 2010-06-05 Revised Ruby Version Monkstone tested on AMD 64 bit linux with nvidia driver
# using lib-opengl-ruby1.9 (ie not the gem version)
require 'opengl'
class Gears
attr_reader :autoexit, :frames, :t0_idle, :t0
Position = Struct.new("Position", :x, :y, :z, :w) # Struct now replace Array
Color = Struct.new("Color", :red, :green, :blue, :alpha)
POS = Position.new(5.0, 5.0, 10.0, 0.0)
RED = Color.new(0.8, 0.1, 0.0, 1.0)
GREEN = Color.new(0.0, 0.8, 0.2, 1.0)
BLUE = Color.new(0.2, 0.2, 1.0, 1.0)
include GL
include GLUT
include Math
# Draw a gear wheel. You'll probably want to call this function when
# building a display list since we do a lot of trig here.
#
# Input: inner_radius - radius of hole at center
# outer_radius - radius at center of teeth
# width - width of gear
# teeth - number of teeth
# tooth_depth - depth of tooth
def gear(inner_radius, outer_radius, width, teeth, tooth_depth)
r0 = inner_radius
r1 = outer_radius - tooth_depth / 2.0
r2 = outer_radius + tooth_depth / 2.0
da = 2.0 * PI / teeth / 4.0
ShadeModel(GL::FLAT)
Normal(0.0, 0.0, 1.0)
# Draw front face
Begin(GL::QUAD_STRIP)
(0..teeth).each do |i|
angle = i * 2.0 * PI / teeth
Vertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5)
Vertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5)
if i < teeth
Vertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5)
Vertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), width * 0.5)
end
end
End()
# Draw front sides of teeth
Begin(GL::QUADS)
(0...teeth).each do |i|
angle = i * 2.0 * PI / teeth
Vertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5)
Vertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5)
Vertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), width * 0.5)
Vertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), width * 0.5)
end
End()
Normal(0.0, 0.0, -1.0)
# Draw back face
Begin(GL::QUAD_STRIP)
(0..teeth).each do |i|
angle = i * 2.0 * PI / teeth
Vertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5)
Vertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5)
if i < teeth
Vertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), -width * 0.5)
Vertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5)
end
end
End()
# Draw back sides of teeth
Begin(GL::QUADS)
(0...teeth).each do |i|
angle = i * 2.0 * PI / teeth
Vertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), -width * 0.5)
Vertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), -width * 0.5)
Vertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5)
Vertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5)
end
End()
# Draw outward faces of teeth
Begin(GL::QUAD_STRIP)
(0...teeth).each do |i|
angle = i * 2.0 * PI / teeth
Vertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5)
Vertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5)
u = r2 * cos(angle + da) - r1 * cos(angle)
v = r2 * sin(angle + da) - r1 * sin(angle)
len = sqrt(u * u + v * v)
u /= len
v /= len
Normal(v, -u, 0.0)
Vertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width * 0.5)
Vertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width * 0.5)
Normal(cos(angle), sin(angle), 0.0)
Vertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), width * 0.5)
Vertex3f(r2 * cos(angle + 2 * da), r2 * sin(angle + 2 * da), -width * 0.5)
u = r1 * cos(angle + 3 * da) - r2 * cos(angle + 2 * da)
v = r1 * sin(angle + 3 * da) - r2 * sin(angle + 2 * da)
Normal(v, -u, 0.0)
Vertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), width * 0.5)
Vertex3f(r1 * cos(angle + 3 * da), r1 * sin(angle + 3 * da), -width * 0.5)
Normal(cos(angle), sin(angle), 0.0)
end
Vertex3f(r1 * cos(0), r1 * sin(0), width * 0.5)
Vertex3f(r1 * cos(0), r1 * sin(0), -width * 0.5)
End()
ShadeModel(GL::SMOOTH)
# Draw inside radius cylinder
Begin(GL::QUAD_STRIP)
(0..teeth).each do |i|
angle = i * 2.0 * PI / teeth
Normal(-cos(angle), -sin(angle), 0.0)
Vertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5)
Vertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5)
end
End()
end
def init
@angle = 0.0
@view_rotx, @view_roty, @view_rotz = 20.0, 30.0, 0.0
Lightfv(GL::LIGHT0, GL::POSITION, POS)
Enable(GL::CULL_FACE)
Enable(GL::LIGHTING)
Enable(GL::LIGHT0)
Enable(GL::DEPTH_TEST)
# Make the gears
@gear1 = GenLists(1)
NewList(@gear1, GL::COMPILE)
Material(GL::FRONT, GL::AMBIENT_AND_DIFFUSE, RED)
gear(1.0, 4.0, 1.0, 20, 0.7)
EndList()
@gear2 = GenLists(1)
NewList(@gear2, GL::COMPILE)
Material(GL::FRONT, GL::AMBIENT_AND_DIFFUSE, GREEN)
gear(0.5, 2.0, 2.0, 10, 0.7)
EndList()
@gear3 = GenLists(1)
NewList(@gear3, GL::COMPILE)
Material(GL::FRONT, GL::AMBIENT_AND_DIFFUSE, BLUE)
gear(1.3, 2.0, 0.5, 10, 0.7)
EndList()
Enable(GL::NORMALIZE)
ARGV.each do |arg|
case arg
when '-info'
puts "GL_RENDERER = #{GetString(GL::RENDERER)}"
puts "GL_VERSION = #{GetString(GL::VERSION)}"
puts "GL_VENDOR = #{GetString(GL::VENDOR)}"
puts "GL_EXTENSIONS = #{GetString(GL::EXTENSIONS)}"
when '-exit'
@autoexit = 30
puts "Auto Exit after #{@autoexit} seconds. "
end
end
end
def initialize
Init()
InitDisplayMode(GLUT::RGB | GLUT::DEPTH | GLUT::DOUBLE)
InitWindowPosition(0, 0)
InitWindowSize(300, 300)
CreateWindow([]) # want's C char array as entry
init()
glut_callbacks()
DisplayFunc(@draw)
ReshapeFunc(@reshape)
KeyboardFunc(@key)
SpecialFunc(@special)
VisibilityFunc(@visible)
MouseFunc(@mouse)
MotionFunc(@motion)
end
def start
MainLoop()
end
end
private
def glut_callbacks
# define GLUT callbacks as ruby 1.9 lambdas (could use Proc.new)
@idle = ->{
t = Get(GLUT::ELAPSED_TIME) / 1000.0
@t0_idle = t unless t0_idle
# 90 degrees per second
@angle += 70.0 * (t - t0_idle)
@t0_idle = t
PostRedisplay()
}
@visible = -> vis {
IdleFunc(vis == GLUT::VISIBLE ? @idle : nil)
}
@mouse = -> button, state, x, y {
@mouse = state
@x0, @y0 = x, y
}
@motion = -> x, y {
if @mouse == GLUT::DOWN
@view_roty += @x0 - x
@view_rotx += @y0 - y
end
@x0, @y0 = x, y
}
# begin draw
@draw = ->{
Clear(GL::COLOR_BUFFER_BIT | GL::DEPTH_BUFFER_BIT);
PushMatrix()
Rotate(@view_rotx, 1.0, 0.0, 0.0)
Rotate(@view_roty, 0.0, 1.0, 0.0)
Rotate(@view_rotz, 0.0, 0.0, 1.0)
PushMatrix()
Translate(-3.0, -2.0, 0.0)
Rotate(@angle, 0.0, 0.0, 1.0)
CallList(@gear1)
PopMatrix()
PushMatrix()
Translate(3.1, -2.0, 0.0)
Rotate(-2.0 * @angle -9.0, 0.0, 0.0, 1.0)
CallList(@gear2)
PopMatrix()
PushMatrix()
Translate(-3.1, 4.2, 0.0)
Rotate(-2.0 * @angle -25.0, 0.0, 0.0, 1.0)
CallList(@gear3)
PopMatrix()
PopMatrix()
SwapBuffers()
@frames = 0 unless frames
@t0 = 0 unless t0
@frames += 1
t = Get(GLUT::ELAPSED_TIME)
if t - t0 >= 5000
seconds = (t - t0) / 1000.0
fps = frames / seconds
puts sprintf("%d frames in %6.3f seconds = %6.3f FPS", frames, seconds, fps)
@t0, @frames = t, 0
autoexit.nil? ? nil : (t <= 999.0 * autoexit) ? nil : exit(0)
end
}
# end draw
# Change view angle, exit upon ESC
@key = -> k, x, y {
case k
when ?z
@view_rotz += 5.0
when ?Z
@view_rotz -= 5.0
when ?\e
exit(0)
end
PostRedisplay()
}
# Change view angle
@special = -> k, x, y{
case k
when GLUT::KEY_UP
@view_rotx += 5.0
when GLUT::KEY_DOWN
@view_rotx -= 5.0
when GLUT::KEY_LEFT
@view_roty += 5.0
when GLUT::KEY_RIGHT
@view_roty -= 5.0
end
PostRedisplay()
}
@reshape = -> width, height{
h = height.to_f / width
Viewport(0, 0, width, height)
MatrixMode(GL::PROJECTION)
LoadIdentity()
Frustum(-1.0, 1.0, -h, h, 5.0, 60.0)
MatrixMode(GL::MODELVIEW)
LoadIdentity()
Translate(0.0, 0.0, -40.0)
}
end
fred = Gears.new
#fred.SetWindowTitle( ###some cstring### ) # how?
fred.start
Unlike ruby-processing there is absolutely no problem resizing (on linux), indeed you can and should experiment with resizing the frame whilst the animation is running. Also the syntax is not too bad makes me think I should explore it a bit more!!!
Experiments with ruby-processing (processing-2.2.1) and JRubyArt for processing-3.0
Tuesday, 6 July 2010
3D Gears: (exploring ruby 1.9 syntax, ruby-opengl bindings, lambda & Struct)
Labels:
lambda,
opengl,
ruby,
stabby-syntax
Subscribe to:
Post Comments (Atom)
Followers
About Me
- monkstone
- I have developed JRubyArt and propane new versions of ruby-processing for JRuby-9.1.5.0 and processing-3.2.2
No comments:
Post a Comment