Opengl Tk widget for Ruby?

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

From: “mpthompson at gmail.com[email protected]
Subject: Opengl Tk widget for Ruby?
Date: Tue, 12 Sep 2006 09:45:34 +0900
Message-ID: [email protected]

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{…})

Hidetoshi NAGAI wrote:

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

mpthompson at gmail.com wrote:

Hidetoshi NAGAI wrote:

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/.

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

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[i]));
GL::Vertex(v[faces[i][0]]);
GL::Vertex(v[faces[i][1]]);
GL::Vertex(v[faces[i][2]]);
GL::Vertex(v[faces[i][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

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs