Opengl Tk widget for Ruby?

  • Thread starter mpthompson at gmail.com
  • Start date
M

mpthompson at gmail.com

I'm would like to create a unified Ruby on Windows XP that will combine
user interface elements of Ruby/Tk and OpenGL. Using Google I've seen
several year old references to a Togl Tk widget that seemed to wrap
OpenGL as a Tk widget, but all the links to it appear to be dead and I
can't find any references that it ever worked on Windows.

I'm have looked into FxRuby as it has OpenGL support built in, but
unfortunately aspects of my user interface are tied to the Tk Canvas
object which provides needed 2D editing functionality for my
application. Is it possible to wrap the Tk canvase within FxRuby? I
assume not.

I would appreciate it if someone provide insight into if it is indeed
possible to wrap an OpenGL view as a Tk widget in Ruby.

I guess my fallback is to create a single application that manages two
windows, one being the OpenGL and the other being Ruby/Tk.

--Mike
 
H

Hidetoshi NAGAI

From: "mpthompson at gmail.com" <[email protected]>
Subject: Opengl Tk widget for Ruby?
Date: Tue, 12 Sep 2006 09:45:34 +0900
Message-ID: said:
I'm would like to create a unified Ruby on Windows XP that will combine
user interface elements of Ruby/Tk and OpenGL. Using Google I've seen
several year old references to a Togl Tk widget that seemed to wrap
OpenGL as a Tk widget, but all the links to it appear to be dead and I
can't find any references that it ever worked on Windows.

There is a section "Windows 95/NT/2000/XP usage" on
<http://togl.sourceforge.net/>.
However, Togl seems to be integrated into Tcl3D <http://www.tcl3d.org/>.

BTW, Ruby/Tk can handle almost all of Tcl/Tk extensions.
If there is no Ruby/Tk's wrapper for a Tcl/Tk extension,
you can send Tcl commands to handle the extension with
Tk.tk_call(...tokens_of_Tcl_command_line...), and so on.

I think that it is not difficult to write your own wrapper.
For example, simple Togl wrapper is
-----------------------------------------------------------
#
# IMPORTANT: NOT TESTED. So, this may not work properly.
#
TkPackage.require('Togl')

class Tk::Togl < TkWindow
# By default, the constracter of a widget class (subclass of TkWindow)
# uses TkCommandNames[0] as a command to create a widget object.
TkCommandNames = ['togl'.freeze].freeze

# The following definitions are used to create a widget object
# from a widget path string.
WidgetClassName = 'Togl'.freeze
WidgetClassNames[WidgetClassName] = self

def render
tk_send_without_enc('render')
self
end

def swapbuffers
tk_send_without_enc('swapbuffers')
self
end

def make_current
tk_send_without_enc('makecurrent')
self
end
end

# f = TkFrame.new
# togl_obj1 = Tk::Togl.new(f, :width=>200, :height=>200, :ident=>'Single',
# :rgba=>true, :double=>false, :depth=>true).pack
# togl_obj2 = Tk::Togl.new(f, :width=>200, :height=>200, :ident=>'Double',
# :sharelist=>'Single',
# :rgba=>true, :double=>true, :depth=>true).pack
# togl_obj1.bind('B1-Motion', proc{....})
 
M

mpthompson at gmail.com

Hidetoshi said:
There is a section "Windows 95/NT/2000/XP usage" on
<http://togl.sourceforge.net/>.
However, Togl seems to be integrated into Tcl3D <http://www.tcl3d.org/>.

BTW, Ruby/Tk can handle almost all of Tcl/Tk extensions.
If there is no Ruby/Tk's wrapper for a Tcl/Tk extension,
you can send Tcl commands to handle the extension with
Tk.tk_call(...tokens_of_Tcl_command_line...), and so on.

Thank you for the information. I'm actually pretty new to Ruby and
know very little about the how it interacts with OpenGL or Tk under the
hood. Writing my own wrappers looks to be a bit daunting, but I guess
I may have to go in that direction.

I did try and use Ruby SDL to accomplish my goal as I saw in a very old
example in this news group from 2001 that showed affixing SDL to a
TkFrame and the SDL displaying OpenGL graphics. However, I ran into an
issue with SDL_WINDOWID not working correctly because of underlying
issues with putenv/getenv with Windows when SDL is run from a DLL.

--Mike
 
M

mpthompson at gmail.com

I think I'm getting close to getting OpenGL and Tk playing nice in
Ruby, but I'm not quite there yet. Any help would be appreciated.

Based on the suggestion above I was able to get the Tcl3D installed
with ActiveTcl 8.4 and get the Tcl based OpenGL demos distributed with
Tcl3D working fine on my system. Next, I used Hidetoshi's code as a
guide to create a simple wrapper, after a bit of fussing to determine
the right way to get the tcl3d dll loaded, I created wrapper for togl
within Tcl3D as shown below which includes an addition for
postredisplay:

---------------------------------------------------------
#
# Togl wrapper for tcl3d Tk extension.
#
TkPackage.require('tcl3d')

class Tk::Togl < TkWindow
# By default, the constracter of a widget class (subclass of
TkWindow)
# uses TkCommandNames[0] as a command to create a widget object.
TkCommandNames = ['togl'.freeze].freeze

# The following definitions are used to create a widget object
# from a widget path string.
WidgetClassName = 'Togl'.freeze
WidgetClassNames[WidgetClassName] = self

def postredisplay
tk_send_without_enc('postredisplay')
self
end

def render
tk_send_without_enc('render')
self
end

def swapbuffers
tk_send_without_enc('swapbuffers')
self
end

def make_current
tk_send_without_enc('makecurrent')
self
end
end


# f = TkFrame.new
# togl_obj1 = Tk::Togl.new(f, :width=>200, :height=>200,
:ident=>'Single',
# :rgba=>true, :double=>false,
:depth=>true).pack
# togl_obj2 = Tk::Togl.new(f, :width=>200, :height=>200,
:ident=>'Double',
# :sharelist=>'Single',
# :rgba=>true, :double=>true,
:depth=>true).pack
# togl_obj1.bind('B1-Motion', proc{....})
---------------------------------------------------------

Now I'm trying to get the standard gears.rb OpenGL application running
within the tk managed window. This is where I'm really getting lost. I
posted below the application as it looks now, but it isn't working and
the window just comes up blank. I assume I'm missing something
fundamental here. I'm hoping someone could either provide a working
example of a Tk/OpenGL application using togl or help steer me in where
my code is failing below.

Thanks,

Mike Thompson

---------------------------------------------------------
# 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

require 'opengl_c'
require 'glut_c'
require 'tk'
require 'tkextlib/tkHTML'
require 'togl'

include OpenGL
include Glut

class Gears

POS = [5.0, 5.0, 10.0, 0.0]
RED = [0.8, 0.1, 0.0, 1.0]
GREEN = [0.0, 0.8, 0.2, 1.0]
BLUE = [0.2, 0.2, 1.0, 1.0]

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

glShadeModel(GL_FLAT)

glNormal(0.0, 0.0, 1.0)

# Draw front face
glBegin(GL_QUAD_STRIP)
for i in 0..teeth
angle = i * 2.0 * PI / teeth
glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5)
glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5)
if i < teeth
glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5)
glVertex3f(r1 * cos(angle + 3 * da),
r1 * sin(angle + 3 * da), width * 0.5)
end
end
glEnd()

# Draw front sides of teeth
glBegin(GL_QUADS)
for i in 0...teeth
angle = i * 2.0 * PI / teeth
glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5)
glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width *
0.5)
glVertex3f(r2 * cos(angle + 2 * da),
r2 * sin(angle + 2 * da), width * 0.5)
glVertex3f(r1 * cos(angle + 3 * da),
r1 * sin(angle + 3 * da), width * 0.5)
end
glEnd()

glNormal(0.0, 0.0, -1.0)

# Draw back face
glBegin(GL_QUAD_STRIP)
for i in 0..teeth
angle = i * 2.0 * PI / teeth
glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5)
glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5)
if i < teeth
glVertex3f(r1 * cos(angle + 3 * da),
r1 * sin(angle + 3 * da), -width * 0.5)
glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5)
end
end
glEnd()

# Draw back sides of teeth
glBegin(GL_QUADS)
for i in 0...teeth
angle = i * 2.0 * PI / teeth
glVertex3f(r1 * cos(angle + 3 * da),
r1 * sin(angle + 3 * da), -width * 0.5)
glVertex3f(r2 * cos(angle + 2 * da),
r2 * sin(angle + 2 * da), -width * 0.5)
glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width *
0.5)
glVertex3f(r1 * cos(angle), r1 * sin(angle), -width * 0.5)
end
glEnd()

# Draw outward faces of teeth
glBegin(GL_QUAD_STRIP)
for i in 0...teeth
angle = i * 2.0 * PI / teeth
glVertex3f(r1 * cos(angle), r1 * sin(angle), width * 0.5)
glVertex3f(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
glNormal(v, -u, 0.0)
glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), width *
0.5)
glVertex3f(r2 * cos(angle + da), r2 * sin(angle + da), -width *
0.5)
glNormal(cos(angle), sin(angle), 0.0)
glVertex3f(r2 * cos(angle + 2 * da),
r2 * sin(angle + 2 * da), width * 0.5)
glVertex3f(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)
glNormal(v, -u, 0.0)
glVertex3f(r1 * cos(angle + 3 * da),
r1 * sin(angle + 3 * da), width * 0.5)
glVertex3f(r1 * cos(angle + 3 * da),
r1 * sin(angle + 3 * da), -width * 0.5)
glNormal(cos(angle), sin(angle), 0.0)
end
glVertex3f(r1 * cos(0), r1 * sin(0), width * 0.5)
glVertex3f(r1 * cos(0), r1 * sin(0), -width * 0.5)
glEnd()

glShadeModel(GL_SMOOTH)

# Draw inside radius cylinder
glBegin(GL_QUAD_STRIP)
for i in 0..teeth
angle = i * 2.0 * PI / teeth
glNormal(-cos(angle), -sin(angle), 0.0)
glVertex3f(r0 * cos(angle), r0 * sin(angle), -width * 0.5)
glVertex3f(r0 * cos(angle), r0 * sin(angle), width * 0.5)
end
glEnd()
end

def draw
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glPushMatrix()
glRotate(@view_rotx, 1.0, 0.0, 0.0)
glRotate(@view_roty, 0.0, 1.0, 0.0)
glRotate(@view_rotz, 0.0, 0.0, 1.0)

glPushMatrix()
glTranslate(-3.0, -2.0, 0.0)
glRotate(@angle, 0.0, 0.0, 1.0)
glCallList(@gear1)
glPopMatrix()

glPushMatrix()
glTranslate(3.1, -2.0, 0.0)
glRotate(-2.0 * @angle - 9.0, 0.0, 0.0, 1.0)
glCallList(@gear2)
glPopMatrix()

glPushMatrix()
glTranslate(-3.1, 4.2, 0.0)
glRotate(-2.0 * @angle - 25.0, 0.0, 0.0, 1.0)
glCallList(@gear3)
glPopMatrix()

glPopMatrix()

@glwin.swapbuffers
end

def idle
#t = glutGet(GLUT_ELAPSED_TIME) / 1000.0
#@t0_idle = t if !defined? @t0_idle
# 90 degrees per second
#@angle += 70.0 * (t - @t0_idle)
#@t0_idle = t
#@glwin.postredisplay
end

# Change view angle, exit upon ESC
def key(k, x, y)
case k
when ?z
@view_rotz += 5.0
when ?Z
@view_rotz -= 5.0
when 27 # Escape
exit
end
@glwin.postredisplay
end

# Change view angle
def 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
@glwin.postredisplay
end

# New window size or exposure
def reshape(width, height)
h = height.to_f / width.to_f
glViewport(0, 0, width, height)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glFrustum(-1.0, 1.0, -h, h, 5.0, 60.0)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glTranslate(0.0, 0.0, -40.0)
end

def init
@angle = 0.0
@view_rotx, @view_roty, @view_rotz = 20.0, 30.0, 0.0

glLightfv(GL_LIGHT0, GL_POSITION, POS)
glEnable(GL_CULL_FACE)
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0)
glEnable(GL_DEPTH_TEST)

# Make the gears
@gear1 = glGenLists(1)
glNewList(@gear1, GL_COMPILE)
glMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, RED)
gear(1.0, 4.0, 1.0, 20, 0.7)
glEndList()

@gear2 = glGenLists(1)
glNewList(@gear2, GL_COMPILE)
glMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, GREEN)
gear(0.5, 2.0, 2.0, 10, 0.7)
glEndList()

@gear3 = glGenLists(1)
glNewList(@gear3, GL_COMPILE)
glMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, BLUE)
gear(1.3, 2.0, 0.5, 10, 0.7)
glEndList()

glEnable(GL_NORMALIZE)

ARGV.each do |arg|
case arg
when '-info'
printf("GL_RENDERER = %s\n", glGetString(GL_RENDERER))
printf("GL_VERSION = %s\n", glGetString(GL_VERSION))
printf("GL_VENDOR = %s\n", glGetString(GL_VENDOR))
printf("GL_EXTENSIONS = %s\n", glGetString(GL_EXTENSIONS))
when '-exit'
@autoexit = 30
printf("Auto Exit after %i seconds.\n", @autoexit);
end
end
end

def visible(vis)
#glutIdleFunc((vis == GLUT_VISIBLE ? method:)idle).to_proc : nil))
end

def mouse(button, state, x, y)
@mouse = state
@x0, @y0 = x, y
end

def motion(x, y)
if @mouse == GLUT_DOWN then
@view_roty += @x0 - x
@view_rotx += @y0 - y
end
@x0, @y0 = x, y
end

def initialize
@root = TkRoot.new
@frame = TkFrame.new(@root)
@glwin = Tk::Togl.new(@frame, :width=>300, :height=>300,
:ident=>'Single',
:rgba=>true, :depth=>true,
:double=>false).pack('fill'=>'both')
init()
end

def start
Tk.mainloop
end

end

Gears.new.start
---------------------------------------------------------
 
M

mpthompson at gmail.com

OK, I think I got getting Ruby Tk and OpenGL figured out -- at least
for the most part. For posterity and the hope it will be useful to
someone else I'll go through the steps of how I got it working below.

Step 1. I installed ActiveTcl 8.4 <http://www.activestate.com/> and
Tcl3D <http://www.tcl3d.org/> installed on my system under the
directory 'C:\Ruby\Tcl'. I then made sure the Tcl3D demos ran fine to
confirm OpenGL was properly working.

Step 2. I added the following lines to my setup.rb file in
'C:\Ruby\lib\ruby\1.8\tkextlib'

---------------------------
Tk::AUTO_PATH.list <<= 'C:/Ruby/Tcl/lib/tcl3d0.3.1/tcl3dCg'
Tk::AUTO_PATH.list <<= 'C:/Ruby/Tcl/lib/tcl3d0.3.1/tcl3dFTGL'
Tk::AUTO_PATH.list <<= 'C:/Ruby/Tcl/lib/tcl3d0.3.1/tcl3dGauges'
Tk::AUTO_PATH.list <<= 'C:/Ruby/Tcl/lib/tcl3d0.3.1/tcl3dGl2ps'
Tk::AUTO_PATH.list <<= 'C:/Ruby/Tcl/lib/tcl3d0.3.1/tcl3dOde'
Tk::AUTO_PATH.list <<= 'C:/Ruby/Tcl/lib/tcl3d0.3.1/tcl3dOgl'
Tk::AUTO_PATH.list <<= 'C:/Ruby/Tcl/lib/tcl3d0.3.1/tcl3dSDL'
Tk::AUTO_PATH.list <<= 'C:/Ruby/Tcl/lib/tcl3d0.3.1/tcl3dTogl'
Tk::AUTO_PATH.list <<= 'C:/Ruby/Tcl/lib/tcl3d0.3.1/tcl3dUtil'
---------------------------

Step 3. I created a togl wrapper class in 'togl.rb' as suggested by
Hidetoshi NAGAI as shown below:

---------------------------
#
# Togl wrapper for tcl3d Tk extension.
#
TkPackage.require('tcl3d')

class Tk::Togl < TkWindow
TkCommandNames = ['togl'.freeze].freeze
WidgetClassName = 'Togl'.freeze
WidgetClassNames[WidgetClassName] = self

def render
tk_send_without_enc('render')
self
end

def swapbuffers
tk_send_without_enc('swapbuffers')
self
end

def make_current
tk_send_without_enc('makecurrent')
self
end

def post_redisplay
tk_send_without_enc('postredisplay')
self
end
end
---------------------------

Step 4. I create a simple 'togltest.rb' application to test OpenGL
working within Tk togl widget:

---------------------------
require 'tk'
require 'togl'
require 'opengl'

def create(toglwin)
light_diffuse = [1.0, 0.0, 0.0, 1.0]
light_position = [1.0, 1.0, 1.0, 0.0]

GL::Light(GL::LIGHT0, GL::DIFFUSE, light_diffuse);
GL::Light(GL::LIGHT0, GL::pOSITION, light_position);
GL::Enable(GL::LIGHT0);
GL::Enable(GL::LIGHTING);
GL::Enable(GL::DEPTH_TEST);

GL::MatrixMode(GL::pROJECTION);
GLU::perspective(40.0, 1.0, 1.0, 10.0);
GL::MatrixMode(GL::MODELVIEW);
GLU::LookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)

GL::Translate(0.0, 0.0, -1.0);
GL::Rotate(60, 1.0, 0.0, 0.0);
GL::Rotate(-20, 0.0, 0.0, 1.0);
end

def reshape(toglwin, width, height)
width = width.to_i
height = height.to_i
if $glwin
$glwin.post_redisplay
end
end

def display(toglwin)
n = [
[-1.0, 0.0, 0.0], [0.0, 1.0, 0.0],
[1.0, 0.0, 0.0], [0.0, -1.0, 0.0],
[0.0, 0.0, 1.0], [0.0, 0.0, -1.0]
]
faces = [
[0, 1, 2, 3], [3, 2, 6, 7], [7, 6, 5, 4],
[4, 5, 1, 0], [5, 6, 2, 1], [7, 4, 0, 3]
]
v = [
[-1, -1,1], [-1, -1,-1],
[-1,1,-1], [-1,1,1],
[1, -1,1], [1, -1,-1],
[1, 1,-1], [1,1,1]
]

GL::Clear(GL::COLOR_BUFFER_BIT | GL::DEPTH_BUFFER_BIT);
for i in (0..5)
GL::Begin(GL::QUADS);
GL::Normal(*(n));
GL::Vertex(v[faces[0]]);
GL::Vertex(v[faces[1]]);
GL::Vertex(v[faces[2]]);
GL::Vertex(v[faces[3]]);
GL::End()
end

$glwin.swapbuffers
end

$root = TkRoot.new
$frame = TkFrame.new($root).pack('fill'=>'both', 'padx'=>10,
'pady'=>10)
$glwin = Tk::Togl.new($frame, 'width'=>300, 'height'=>300,
'ident'=>'Single',
'rgba'=>true, 'depth'=>true, 'double'=>true,
'createproc'=>proc {|toglwin| create(toglwin)},
'displayproc'=>proc {|toglwin| display(toglwin)},
'reshapeproc'=>proc {|toglwin, width, height|
reshape(toglwin, width, height)}
).pack('fill'=>'both')

Tk.mainloop
---------------------------

That's it. When you run the Ruby Tk application a window with an
OpenGL 3D red block should appear.

I hope someone else finds this information useful.

Mike Thompson
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top